ruby_smb 1.1.0 → 2.0.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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -4
- data/.travis.yml +3 -5
- data/Gemfile +6 -2
- data/examples/negotiate.rb +51 -8
- data/examples/read_file_encryption.rb +56 -0
- data/lib/ruby_smb.rb +4 -0
- data/lib/ruby_smb/client.rb +172 -16
- data/lib/ruby_smb/client/authentication.rb +27 -8
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +133 -12
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +4 -4
- data/lib/ruby_smb/client/utils.rb +8 -7
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dispatcher/socket.rb +2 -2
- data/lib/ruby_smb/error.rb +28 -1
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +4 -4
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
- data/lib/ruby_smb/smb1/pipe.rb +2 -2
- data/lib/ruby_smb/smb1/tree.rb +3 -3
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
- data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
- data/lib/ruby_smb/smb2/file.rb +25 -43
- data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
- data/lib/ruby_smb/smb2/packet.rb +2 -0
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +49 -3
- data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
- data/lib/ruby_smb/smb2/pipe.rb +3 -16
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +23 -17
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +3 -1
- data/spec/lib/ruby_smb/client_spec.rb +1256 -57
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/error_spec.rb +59 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
- data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
- data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +0 -40
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
- metadata +124 -75
- metadata.gz.sig +0 -0
data/lib/ruby_smb/smb2/pipe.rb
CHANGED
@@ -46,7 +46,7 @@ module RubySMB
|
|
46
46
|
end
|
47
47
|
|
48
48
|
unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
49
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
49
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
50
50
|
end
|
51
51
|
response
|
52
52
|
end
|
@@ -101,22 +101,9 @@ module RubySMB
|
|
101
101
|
received_cmd: ioctl_response.smb2_header.command
|
102
102
|
)
|
103
103
|
end
|
104
|
-
|
105
|
-
if ioctl_response.status_code == WindowsError::NTStatus::STATUS_PENDING
|
106
|
-
sleep 1
|
107
|
-
ioctl_raw_response = @tree.client.dispatcher.recv_packet
|
108
|
-
ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
|
109
|
-
unless ioctl_response.valid?
|
110
|
-
raise RubySMB::Error::InvalidPacket.new(
|
111
|
-
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
112
|
-
expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
|
113
|
-
received_proto: ioctl_response.smb2_header.protocol,
|
114
|
-
received_cmd: ioctl_response.smb2_header.command
|
115
|
-
)
|
116
|
-
end
|
117
|
-
elsif ![WindowsError::NTStatus::STATUS_SUCCESS,
|
104
|
+
unless [WindowsError::NTStatus::STATUS_SUCCESS,
|
118
105
|
WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
|
119
|
-
raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
|
106
|
+
raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
|
120
107
|
end
|
121
108
|
|
122
109
|
raw_data = ioctl_response.output_data
|
@@ -6,7 +6,7 @@ module RubySMB
|
|
6
6
|
endian :little
|
7
7
|
bit32 :protocol, label: 'Protocol ID Field', initial_value: RubySMB::SMB2::SMB2_PROTOCOL_ID
|
8
8
|
uint16 :structure_size, label: 'Header Structure Size', initial_value: 64
|
9
|
-
uint16 :credit_charge, label: 'Credit Charge', initial_value:
|
9
|
+
uint16 :credit_charge, label: 'Credit Charge', initial_value: 1
|
10
10
|
nt_status :nt_status, label: 'NT Status', initial_value: 0
|
11
11
|
uint16 :command, label: 'Command'
|
12
12
|
uint16 :credits, label: 'Credit Request/Response'
|
data/lib/ruby_smb/smb2/tree.rb
CHANGED
@@ -23,12 +23,18 @@ module RubySMB
|
|
23
23
|
# @return [Integer]
|
24
24
|
attr_accessor :id
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
# Whether or not encryption is required (SMB 3.x)
|
27
|
+
# @!attribute [rw] encryption_required
|
28
|
+
# @return [Boolean]
|
29
|
+
attr_accessor :encryption_required
|
30
|
+
|
31
|
+
def initialize(client:, share:, response:, encrypt: false)
|
32
|
+
@client = client
|
33
|
+
@share = share
|
34
|
+
@id = response.smb2_header.tree_id
|
35
|
+
@permissions = response.maximal_access
|
36
|
+
@share_type = response.share_type
|
37
|
+
@encryption_required = encrypt
|
32
38
|
end
|
33
39
|
|
34
40
|
# Disconnects this Tree from the current session
|
@@ -38,7 +44,7 @@ module RubySMB
|
|
38
44
|
def disconnect!
|
39
45
|
request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
|
40
46
|
request = set_header_fields(request)
|
41
|
-
raw_response = client.send_recv(request)
|
47
|
+
raw_response = client.send_recv(request, encrypt: @encryption_required)
|
42
48
|
response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
|
43
49
|
unless response.valid?
|
44
50
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -96,7 +102,7 @@ module RubySMB
|
|
96
102
|
create_request.create_disposition = disposition
|
97
103
|
create_request.name = filename
|
98
104
|
|
99
|
-
raw_response = client.send_recv(create_request)
|
105
|
+
raw_response = client.send_recv(create_request, encrypt: @encryption_required)
|
100
106
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
101
107
|
unless response.valid?
|
102
108
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -107,15 +113,15 @@ module RubySMB
|
|
107
113
|
)
|
108
114
|
end
|
109
115
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
110
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
116
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
111
117
|
end
|
112
118
|
|
113
119
|
case @share_type
|
114
|
-
when
|
115
|
-
RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
|
116
|
-
when
|
120
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
|
121
|
+
RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @encryption_required)
|
122
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
|
117
123
|
RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
|
118
|
-
# when
|
124
|
+
# when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
|
119
125
|
# it's a printer!
|
120
126
|
else
|
121
127
|
raise RubySMB::Error::RubySMBError, 'Unsupported share type'
|
@@ -148,7 +154,7 @@ module RubySMB
|
|
148
154
|
files = []
|
149
155
|
|
150
156
|
loop do
|
151
|
-
response = client.send_recv(directory_request)
|
157
|
+
response = client.send_recv(directory_request, encrypt: @encryption_required)
|
152
158
|
directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
|
153
159
|
unless directory_response.valid?
|
154
160
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -164,7 +170,7 @@ module RubySMB
|
|
164
170
|
break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
|
165
171
|
|
166
172
|
unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
167
|
-
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
173
|
+
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
168
174
|
end
|
169
175
|
|
170
176
|
files += directory_response.results(type)
|
@@ -193,7 +199,7 @@ module RubySMB
|
|
193
199
|
|
194
200
|
create_request = open_directory_packet(directory: directory, disposition: disposition,
|
195
201
|
impersonation: impersonation, read: read, write: write, delete: delete)
|
196
|
-
raw_response = client.send_recv(create_request)
|
202
|
+
raw_response = client.send_recv(create_request, encrypt: @encryption_required)
|
197
203
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
198
204
|
unless response.valid?
|
199
205
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -204,7 +210,7 @@ module RubySMB
|
|
204
210
|
)
|
205
211
|
end
|
206
212
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
207
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
213
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
208
214
|
end
|
209
215
|
|
210
216
|
response
|
data/lib/ruby_smb/version.rb
CHANGED
data/ruby_smb.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.platform = Gem::Platform::RUBY
|
27
27
|
end
|
28
28
|
|
29
|
-
spec.required_ruby_version = '>= 2.
|
29
|
+
spec.required_ruby_version = '>= 2.5'
|
30
30
|
|
31
31
|
spec.add_development_dependency 'bundler'
|
32
32
|
spec.add_development_dependency 'fivemat'
|
@@ -36,4 +36,6 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_runtime_dependency 'rubyntlm'
|
37
37
|
spec.add_runtime_dependency 'windows_error'
|
38
38
|
spec.add_runtime_dependency 'bindata'
|
39
|
+
spec.add_runtime_dependency 'openssl-ccm'
|
40
|
+
spec.add_runtime_dependency 'openssl-cmac'
|
39
41
|
end
|
@@ -6,11 +6,66 @@ RSpec.describe RubySMB::Client do
|
|
6
6
|
let(:username) { 'msfadmin' }
|
7
7
|
let(:password) { 'msfpasswd' }
|
8
8
|
subject(:client) { described_class.new(dispatcher, username: username, password: password) }
|
9
|
-
let(:smb1_client) { described_class.new(dispatcher, smb2: false, username: username, password: password) }
|
10
|
-
let(:smb2_client) { described_class.new(dispatcher, smb1: false, username: username, password: password) }
|
9
|
+
let(:smb1_client) { described_class.new(dispatcher, smb2: false, smb3: false, username: username, password: password) }
|
10
|
+
let(:smb2_client) { described_class.new(dispatcher, smb1: false, smb3: false, username: username, password: password) }
|
11
|
+
let(:smb3_client) { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }
|
11
12
|
let(:empty_packet) { RubySMB::SMB1::Packet::EmptyPacket.new }
|
12
13
|
let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
|
13
14
|
|
15
|
+
it { is_expected.to respond_to :dispatcher }
|
16
|
+
it { is_expected.to respond_to :domain }
|
17
|
+
it { is_expected.to respond_to :local_workstation }
|
18
|
+
it { is_expected.to respond_to :ntlm_client }
|
19
|
+
it { is_expected.to respond_to :password }
|
20
|
+
it { is_expected.to respond_to :peer_native_os }
|
21
|
+
it { is_expected.to respond_to :peer_native_lm }
|
22
|
+
it { is_expected.to respond_to :primary_domain }
|
23
|
+
it { is_expected.to respond_to :default_name }
|
24
|
+
it { is_expected.to respond_to :default_domain }
|
25
|
+
it { is_expected.to respond_to :dns_host_name }
|
26
|
+
it { is_expected.to respond_to :dns_domain_name }
|
27
|
+
it { is_expected.to respond_to :dns_tree_name }
|
28
|
+
it { is_expected.to respond_to :os_version }
|
29
|
+
it { is_expected.to respond_to :dialect }
|
30
|
+
it { is_expected.to respond_to :sequence_counter }
|
31
|
+
it { is_expected.to respond_to :session_id }
|
32
|
+
it { is_expected.to respond_to :signing_required }
|
33
|
+
it { is_expected.to respond_to :smb1 }
|
34
|
+
it { is_expected.to respond_to :smb2 }
|
35
|
+
it { is_expected.to respond_to :smb3 }
|
36
|
+
it { is_expected.to respond_to :smb2_message_id }
|
37
|
+
it { is_expected.to respond_to :username }
|
38
|
+
it { is_expected.to respond_to :user_id }
|
39
|
+
it { is_expected.to respond_to :max_buffer_size }
|
40
|
+
it { is_expected.to respond_to :server_max_buffer_size }
|
41
|
+
it { is_expected.to respond_to :server_max_write_size }
|
42
|
+
it { is_expected.to respond_to :server_max_read_size }
|
43
|
+
it { is_expected.to respond_to :server_max_transact_size }
|
44
|
+
it { is_expected.to respond_to :preauth_integrity_hash_algorithm }
|
45
|
+
it { is_expected.to respond_to :preauth_integrity_hash_value }
|
46
|
+
it { is_expected.to respond_to :encryption_algorithm }
|
47
|
+
it { is_expected.to respond_to :client_encryption_key }
|
48
|
+
it { is_expected.to respond_to :server_encryption_key }
|
49
|
+
it { is_expected.to respond_to :encryption_required }
|
50
|
+
it { is_expected.to respond_to :server_encryption_algorithms }
|
51
|
+
it { is_expected.to respond_to :server_compression_algorithms }
|
52
|
+
it { is_expected.to respond_to :negotiated_smb_version }
|
53
|
+
it { is_expected.to respond_to :session_key }
|
54
|
+
it { is_expected.to respond_to :tree_connects }
|
55
|
+
it { is_expected.to respond_to :open_files }
|
56
|
+
it { is_expected.to respond_to :use_ntlmv2 }
|
57
|
+
it { is_expected.to respond_to :usentlm2_session }
|
58
|
+
it { is_expected.to respond_to :send_lm }
|
59
|
+
it { is_expected.to respond_to :use_lanman_key }
|
60
|
+
it { is_expected.to respond_to :send_ntlm }
|
61
|
+
it { is_expected.to respond_to :spnopt }
|
62
|
+
it { is_expected.to respond_to :evasion_opts }
|
63
|
+
it { is_expected.to respond_to :native_os }
|
64
|
+
it { is_expected.to respond_to :native_lm }
|
65
|
+
it { is_expected.to respond_to :verify_signature }
|
66
|
+
it { is_expected.to respond_to :auth_user }
|
67
|
+
it { is_expected.to respond_to :last_file_id }
|
68
|
+
|
14
69
|
describe '#initialize' do
|
15
70
|
it 'should raise an ArgumentError without a valid dispatcher' do
|
16
71
|
expect { described_class.new(nil) }.to raise_error(ArgumentError)
|
@@ -26,14 +81,21 @@ RSpec.describe RubySMB::Client do
|
|
26
81
|
|
27
82
|
it 'accepts an argument to disable smb1 support' do
|
28
83
|
expect(smb2_client.smb1).to be false
|
84
|
+
expect(smb3_client.smb1).to be false
|
29
85
|
end
|
30
86
|
|
31
87
|
it 'accepts an argument to disable smb2 support' do
|
32
88
|
expect(smb1_client.smb2).to be false
|
89
|
+
expect(smb3_client.smb2).to be false
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'accepts an argument to disable smb3 support' do
|
93
|
+
expect(smb1_client.smb3).to be false
|
94
|
+
expect(smb2_client.smb3).to be false
|
33
95
|
end
|
34
96
|
|
35
|
-
it 'raises an exception if
|
36
|
-
expect { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
|
97
|
+
it 'raises an exception if SMB1, SMB2 and SMB3 are disabled' do
|
98
|
+
expect { described_class.new(dispatcher, smb1: false, smb2: false, smb3: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
|
37
99
|
end
|
38
100
|
|
39
101
|
it 'sets the username attribute' do
|
@@ -44,6 +106,11 @@ RSpec.describe RubySMB::Client do
|
|
44
106
|
expect(client.password).to eq password
|
45
107
|
end
|
46
108
|
|
109
|
+
it 'sets the encryption_required attribute' do
|
110
|
+
client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
|
111
|
+
expect(client.encryption_required).to eq true
|
112
|
+
end
|
113
|
+
|
47
114
|
it 'creates an NTLM client' do
|
48
115
|
expect(client.ntlm_client).to be_a Net::NTLM::Client
|
49
116
|
end
|
@@ -76,13 +143,52 @@ RSpec.describe RubySMB::Client do
|
|
76
143
|
end
|
77
144
|
end
|
78
145
|
|
146
|
+
describe '#echo' do
|
147
|
+
let(:response) { double('Echo Response') }
|
148
|
+
before :example do
|
149
|
+
allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'with SMB1' do
|
153
|
+
it 'calls #smb1_echo with the expected arguments' do
|
154
|
+
allow(smb1_client).to receive(:smb1_echo).and_return(response)
|
155
|
+
count = 3
|
156
|
+
data = 'testing...'
|
157
|
+
smb1_client.echo(count: count, data: data)
|
158
|
+
expect(smb1_client).to have_received(:smb1_echo).with(count: count, data: data)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'with SMB2' do
|
163
|
+
it 'calls #smb2_echo without arguments' do
|
164
|
+
allow(smb2_client).to receive(:smb2_echo).and_return(response)
|
165
|
+
smb2_client.echo
|
166
|
+
expect(smb2_client).to have_received(:smb2_echo)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'with SMB3' do
|
171
|
+
it 'calls #smb2_echo without arguments' do
|
172
|
+
allow(smb3_client).to receive(:smb2_echo).and_return(response)
|
173
|
+
smb3_client.echo
|
174
|
+
expect(smb3_client).to have_received(:smb2_echo)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'returns the expected status code' do
|
179
|
+
allow(smb2_client).to receive(:smb2_echo).and_return(response)
|
180
|
+
expect(smb2_client.echo).to eq(WindowsError::NTStatus::STATUS_SUCCESS)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
79
184
|
describe '#send_recv' do
|
80
185
|
let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
|
81
186
|
let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
|
82
187
|
|
83
188
|
before(:each) do
|
84
|
-
|
85
|
-
|
189
|
+
allow(client).to receive(:is_status_pending?).and_return(false)
|
190
|
+
allow(dispatcher).to receive(:send_packet).and_return(nil)
|
191
|
+
allow(dispatcher).to receive(:recv_packet).and_return('A')
|
86
192
|
end
|
87
193
|
|
88
194
|
it 'checks the packet version' do
|
@@ -90,14 +196,218 @@ RSpec.describe RubySMB::Client do
|
|
90
196
|
client.send_recv(smb1_request)
|
91
197
|
end
|
92
198
|
|
93
|
-
|
94
|
-
|
199
|
+
context 'when signing' do
|
200
|
+
it 'calls #smb1_sign if it is an SMB1 packet' do
|
201
|
+
expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
|
202
|
+
client.send_recv(smb1_request)
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'with an SMB2 packet' do
|
206
|
+
it 'does not sign a SessionSetupRequest packet' do
|
207
|
+
expect(smb2_client).to_not receive(:smb2_sign)
|
208
|
+
expect(smb2_client).to_not receive(:smb3_sign)
|
209
|
+
client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'calls #smb2_sign if it is an SMB2 client' do
|
213
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
214
|
+
expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
|
215
|
+
smb2_client.send_recv(smb2_request)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'calls #smb3_sign if it is an SMB3 client' do
|
219
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
220
|
+
expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
|
221
|
+
smb3_client.send_recv(smb2_request)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'sends the expected packet and gets the response' do
|
227
|
+
expect(dispatcher).to receive(:send_packet).with(smb1_request)
|
228
|
+
expect(dispatcher).to receive(:recv_packet)
|
95
229
|
client.send_recv(smb1_request)
|
96
230
|
end
|
97
231
|
|
98
|
-
|
99
|
-
|
100
|
-
|
232
|
+
context 'with SMB1' do
|
233
|
+
it 'does not check if it is a STATUS_PENDING response' do
|
234
|
+
expect(smb1_client).to_not receive(:is_status_pending?)
|
235
|
+
smb1_client.send_recv(smb1_request)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'with SMB2' do
|
240
|
+
context 'when receiving a STATUS_PENDING response' do
|
241
|
+
it 'waits 1 second and reads/decrypts again' do
|
242
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
|
243
|
+
expect(smb2_client).to receive(:sleep).with(1)
|
244
|
+
expect(dispatcher).to receive(:recv_packet).twice
|
245
|
+
smb2_client.send_recv(smb2_request)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'with SMB3 and encryption' do
|
251
|
+
before :example do
|
252
|
+
smb3_client.dialect = '0x0300'
|
253
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'with a SessionSetupRequest' do
|
257
|
+
it 'does not encrypt/decrypt' do
|
258
|
+
request = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
259
|
+
expect(smb3_client).to_not receive(:send_encrypt).with(request)
|
260
|
+
expect(smb3_client).to_not receive(:recv_encrypt)
|
261
|
+
expect(dispatcher).to receive(:send_packet).with(request)
|
262
|
+
expect(dispatcher).to receive(:recv_packet)
|
263
|
+
smb3_client.send_recv(request)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'with a NegotiateRequest' do
|
268
|
+
it 'does not encrypt/decrypt' do
|
269
|
+
request = RubySMB::SMB2::Packet::NegotiateRequest.new
|
270
|
+
expect(smb3_client).to_not receive(:send_encrypt).with(request)
|
271
|
+
expect(smb3_client).to_not receive(:recv_encrypt)
|
272
|
+
expect(dispatcher).to receive(:send_packet).with(request)
|
273
|
+
expect(dispatcher).to receive(:recv_packet)
|
274
|
+
smb3_client.send_recv(request)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'encrypts and decrypts' do
|
279
|
+
expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
|
280
|
+
expect(smb3_client).to receive(:recv_encrypt)
|
281
|
+
smb3_client.send_recv(smb2_request)
|
282
|
+
end
|
283
|
+
|
284
|
+
context 'when receiving a STATUS_PENDING response' do
|
285
|
+
it 'waits 1 second and reads/decrypts again' do
|
286
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
|
287
|
+
expect(smb3_client).to receive(:sleep).with(1)
|
288
|
+
expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
|
289
|
+
expect(smb3_client).to receive(:recv_encrypt).twice
|
290
|
+
smb3_client.send_recv(smb2_request)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
describe '#is_status_pending?' do
|
297
|
+
let(:response) {
|
298
|
+
res = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
299
|
+
res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
|
300
|
+
res.smb2_header.flags.async_command = 1
|
301
|
+
res
|
302
|
+
}
|
303
|
+
|
304
|
+
it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
|
305
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be true
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
|
309
|
+
response.smb2_header.flags.async_command = 0
|
310
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be false
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
|
314
|
+
response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
|
315
|
+
expect(client.is_status_pending?(response.to_binary_s)).to be false
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe '#can_be_encrypted?' do
|
320
|
+
it 'returns true if the packet can be encrypted' do
|
321
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
322
|
+
expect(client.can_be_encrypted?(packet)).to be true
|
323
|
+
end
|
324
|
+
|
325
|
+
[RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
|
326
|
+
it "returns false if the packet is a #{klass}" do
|
327
|
+
packet = klass.new
|
328
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
describe '#encryption_supported?' do
|
334
|
+
['0x0300', '0x0302', '0x0311'].each do |dialect|
|
335
|
+
it "returns true if the dialect is #{dialect}" do
|
336
|
+
client.dialect = dialect
|
337
|
+
expect(client.encryption_supported?).to be true
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
it "returns false if the dialect does not support encryption" do
|
342
|
+
client.dialect = '0x0202'
|
343
|
+
expect(client.encryption_supported?).to be false
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
describe '#send_encrypt' do
|
348
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
349
|
+
before :example do
|
350
|
+
allow(dispatcher).to receive(:send_packet)
|
351
|
+
client.dialect = '0x0300'
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'creates a Transform request' do
|
355
|
+
expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
|
356
|
+
client.send_encrypt(packet)
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'raises an EncryptionError exception if an error occurs while encrypting' do
|
360
|
+
allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
|
361
|
+
expect { client.send_encrypt(packet) }.to raise_error(
|
362
|
+
RubySMB::Error::EncryptionError,
|
363
|
+
"Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
|
364
|
+
)
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'sends the encrypted packet' do
|
368
|
+
encrypted_packet = double('Encrypted packet')
|
369
|
+
allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
|
370
|
+
client.send_encrypt(packet)
|
371
|
+
expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
describe '#recv_encrypt' do
|
376
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
377
|
+
before :example do
|
378
|
+
allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
|
379
|
+
client.dialect = '0x0300'
|
380
|
+
allow(client).to receive(:smb3_decrypt)
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'reads the response packet' do
|
384
|
+
client.recv_encrypt
|
385
|
+
expect(dispatcher).to have_received(:recv_packet)
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'parses the response as a Transform response packet' do
|
389
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
|
390
|
+
client.recv_encrypt
|
391
|
+
end
|
392
|
+
|
393
|
+
it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
|
394
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
|
395
|
+
expect { client.recv_encrypt}.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'decrypts the Transform response packet' do
|
399
|
+
transform = double('Transform header packet')
|
400
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
|
401
|
+
client.recv_encrypt
|
402
|
+
expect(client).to have_received(:smb3_decrypt).with(transform)
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'raises an EncryptionError exception if an error occurs while decrypting' do
|
406
|
+
allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError )
|
407
|
+
expect { client.recv_encrypt}.to raise_error(
|
408
|
+
RubySMB::Error::EncryptionError,
|
409
|
+
"Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError"
|
410
|
+
)
|
101
411
|
end
|
102
412
|
end
|
103
413
|
|
@@ -240,6 +550,44 @@ RSpec.describe RubySMB::Client do
|
|
240
550
|
expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
241
551
|
end
|
242
552
|
end
|
553
|
+
|
554
|
+
context 'with SMB3' do
|
555
|
+
let(:raw_response) { double('Raw response') }
|
556
|
+
let(:logoff_response) {
|
557
|
+
RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
|
558
|
+
}
|
559
|
+
before :example do
|
560
|
+
allow(smb3_client).to receive(:send_recv).and_return(raw_response)
|
561
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
562
|
+
allow(smb3_client).to receive(:wipe_state!)
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'creates a LogoffRequest packet' do
|
566
|
+
expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
|
567
|
+
smb3_client.logoff!
|
568
|
+
end
|
569
|
+
|
570
|
+
it 'calls #send_recv' do
|
571
|
+
expect(smb3_client).to receive(:send_recv)
|
572
|
+
smb3_client.logoff!
|
573
|
+
end
|
574
|
+
|
575
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
576
|
+
expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
577
|
+
smb3_client.logoff!
|
578
|
+
end
|
579
|
+
|
580
|
+
it 'raise an InvalidPacket exception when the response is an error packet' do
|
581
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
|
582
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
583
|
+
end
|
584
|
+
|
585
|
+
it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
|
586
|
+
logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
587
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
588
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
589
|
+
end
|
590
|
+
end
|
243
591
|
end
|
244
592
|
|
245
593
|
context 'NetBIOS Session Service' do
|
@@ -365,7 +713,8 @@ RSpec.describe RubySMB::Client do
|
|
365
713
|
smb1_extended_response.to_binary_s
|
366
714
|
}
|
367
715
|
|
368
|
-
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
|
716
|
+
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
|
717
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
|
369
718
|
|
370
719
|
describe '#smb1_negotiate_request' do
|
371
720
|
it 'returns an SMB1 Negotiate Request packet' do
|
@@ -373,33 +722,158 @@ RSpec.describe RubySMB::Client do
|
|
373
722
|
end
|
374
723
|
|
375
724
|
it 'sets the default SMB1 Dialect' do
|
376
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
725
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
726
|
+
buffer_format: 2,
|
727
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
728
|
+
)
|
377
729
|
end
|
378
730
|
|
379
731
|
it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
|
380
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
732
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
733
|
+
buffer_format: 2,
|
734
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
735
|
+
)
|
381
736
|
end
|
382
737
|
|
383
738
|
it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
|
384
|
-
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
739
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
740
|
+
buffer_format: 2,
|
741
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
742
|
+
)
|
385
743
|
end
|
386
744
|
|
387
745
|
it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
|
388
|
-
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
746
|
+
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
747
|
+
buffer_format: 2,
|
748
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
749
|
+
)
|
750
|
+
end
|
751
|
+
|
752
|
+
it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
|
753
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
754
|
+
buffer_format: 2,
|
755
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
756
|
+
)
|
757
|
+
end
|
758
|
+
|
759
|
+
it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
|
760
|
+
expect(smb3_client.smb1_negotiate_request.dialects).to include(
|
761
|
+
buffer_format: 2,
|
762
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
763
|
+
)
|
764
|
+
end
|
765
|
+
|
766
|
+
it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
|
767
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
768
|
+
buffer_format: 2,
|
769
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
770
|
+
)
|
389
771
|
end
|
390
772
|
end
|
391
773
|
|
392
|
-
describe '#
|
774
|
+
describe '#smb2_3_negotiate_request' do
|
393
775
|
it 'return an SMB2 Negotiate Request packet' do
|
394
|
-
expect(client.
|
776
|
+
expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
|
395
777
|
end
|
396
778
|
|
397
|
-
it 'sets the default SMB2 Dialect' do
|
398
|
-
expect(client.
|
779
|
+
it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
|
780
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
781
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
782
|
+
)
|
783
|
+
end
|
784
|
+
|
785
|
+
it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
|
786
|
+
expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
|
787
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
788
|
+
)
|
399
789
|
end
|
400
790
|
|
401
791
|
it 'sets the Message ID to 0' do
|
402
|
-
expect(client.
|
792
|
+
expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
|
793
|
+
end
|
794
|
+
|
795
|
+
it 'adds SMB3 dialects if if SMB3 support is enabled' do
|
796
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
797
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
798
|
+
)
|
799
|
+
end
|
800
|
+
|
801
|
+
it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
|
802
|
+
expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
|
803
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
804
|
+
)
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
describe '#add_smb3_to_negotiate_request' do
|
809
|
+
let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
|
810
|
+
|
811
|
+
it 'adds the default SMB3 dialects' do
|
812
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
|
813
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
814
|
+
)
|
815
|
+
end
|
816
|
+
|
817
|
+
it 'raises the expected exception when the dialects is not an array of strings' do
|
818
|
+
dialects = ['0x0300', 0x0302, '0x0311']
|
819
|
+
expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
|
820
|
+
end
|
821
|
+
|
822
|
+
it 'sets encryption capability flag' do
|
823
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
|
824
|
+
end
|
825
|
+
|
826
|
+
context 'when the negotiate packet includes the 0x0311 dialect' do
|
827
|
+
before :example do
|
828
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
|
829
|
+
end
|
830
|
+
|
831
|
+
it 'adds 3 Negotiate Contexts' do
|
832
|
+
expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
|
833
|
+
end
|
834
|
+
|
835
|
+
it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
|
836
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
837
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
838
|
+
end
|
839
|
+
expect(nc.length).to eq(1)
|
840
|
+
expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
|
841
|
+
end
|
842
|
+
|
843
|
+
it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
|
844
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
845
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
846
|
+
end
|
847
|
+
expect(nc.length).to eq(1)
|
848
|
+
expect(nc.first.data.ciphers).to eq(
|
849
|
+
[
|
850
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
|
851
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
852
|
+
]
|
853
|
+
)
|
854
|
+
end
|
855
|
+
|
856
|
+
it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
|
857
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
858
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
859
|
+
end
|
860
|
+
expect(nc.length).to eq(1)
|
861
|
+
expect(nc.first.data.compression_algorithms).to eq(
|
862
|
+
[
|
863
|
+
RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
864
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77,
|
865
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
|
866
|
+
RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
867
|
+
]
|
868
|
+
)
|
869
|
+
end
|
870
|
+
end
|
871
|
+
|
872
|
+
context 'when the negotiate packet does not include the 0x0311 dialect' do
|
873
|
+
it 'does not add any Negotiate Context' do
|
874
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
|
875
|
+
expect(negotiate_request.negotiate_context_list?). to be false
|
876
|
+
end
|
403
877
|
end
|
404
878
|
end
|
405
879
|
|
@@ -414,10 +888,15 @@ RSpec.describe RubySMB::Client do
|
|
414
888
|
client.negotiate_request
|
415
889
|
end
|
416
890
|
|
417
|
-
it 'calls #
|
418
|
-
expect(smb2_client).to receive(:
|
891
|
+
it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
|
892
|
+
expect(smb2_client).to receive(:smb2_3_negotiate_request)
|
419
893
|
smb2_client.negotiate_request
|
420
894
|
end
|
895
|
+
|
896
|
+
it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
|
897
|
+
expect(smb3_client).to receive(:smb2_3_negotiate_request)
|
898
|
+
smb3_client.negotiate_request
|
899
|
+
end
|
421
900
|
end
|
422
901
|
|
423
902
|
describe '#negotiate_response' do
|
@@ -464,12 +943,28 @@ RSpec.describe RubySMB::Client do
|
|
464
943
|
end
|
465
944
|
end
|
466
945
|
|
467
|
-
context 'with
|
946
|
+
context 'with only SMB3' do
|
947
|
+
it 'returns a properly formed packet' do
|
948
|
+
expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
949
|
+
end
|
950
|
+
|
951
|
+
it 'raises an exception if the Response is invalid' do
|
952
|
+
expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
953
|
+
end
|
954
|
+
|
955
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
956
|
+
bogus_response = smb2_response
|
957
|
+
bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
958
|
+
expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
959
|
+
end
|
960
|
+
end
|
961
|
+
|
962
|
+
context 'with SMB1, SMB2 and SMB3 enabled' do
|
468
963
|
it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
|
469
964
|
expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
470
965
|
end
|
471
966
|
|
472
|
-
it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
|
967
|
+
it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
|
473
968
|
expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
474
969
|
end
|
475
970
|
end
|
@@ -477,9 +972,10 @@ RSpec.describe RubySMB::Client do
|
|
477
972
|
|
478
973
|
describe '#parse_negotiate_response' do
|
479
974
|
context 'when SMB1 was Negotiated' do
|
480
|
-
it 'turns off SMB2 support' do
|
975
|
+
it 'turns off SMB2 and SMB3 support' do
|
481
976
|
client.parse_negotiate_response(smb1_extended_response)
|
482
977
|
expect(client.smb2).to be false
|
978
|
+
expect(client.smb3).to be false
|
483
979
|
end
|
484
980
|
|
485
981
|
it 'sets whether or not signing is required' do
|
@@ -502,12 +998,18 @@ RSpec.describe RubySMB::Client do
|
|
502
998
|
it 'returns the string \'SMB1\'' do
|
503
999
|
expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
|
504
1000
|
end
|
1001
|
+
|
1002
|
+
it 'sets #negotiated_smb_version to 1' do
|
1003
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1004
|
+
expect(client.negotiated_smb_version).to eq(1)
|
1005
|
+
end
|
505
1006
|
end
|
506
1007
|
|
507
1008
|
context 'when SMB2 was negotiated' do
|
508
|
-
it 'turns off SMB1 support' do
|
1009
|
+
it 'turns off SMB1 and SMB3 support' do
|
509
1010
|
client.parse_negotiate_response(smb2_response)
|
510
1011
|
expect(client.smb1).to be false
|
1012
|
+
expect(client.smb3).to be false
|
511
1013
|
end
|
512
1014
|
|
513
1015
|
it 'sets whether or not signing is required' do
|
@@ -526,9 +1028,81 @@ RSpec.describe RubySMB::Client do
|
|
526
1028
|
expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
|
527
1029
|
end
|
528
1030
|
end
|
1031
|
+
|
1032
|
+
context 'when SMB3 was negotiated' do
|
1033
|
+
it 'turns off SMB1 and SMB2 support' do
|
1034
|
+
client.parse_negotiate_response(smb3_response)
|
1035
|
+
expect(client.smb1).to be false
|
1036
|
+
expect(client.smb2).to be false
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
it 'sets whether or not signing is required' do
|
1040
|
+
smb3_response.security_mode.signing_required = 1
|
1041
|
+
client.parse_negotiate_response(smb3_response)
|
1042
|
+
expect(client.signing_required).to be true
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
it 'sets #dialect to the negotiated dialect' do
|
1046
|
+
client.parse_negotiate_response(smb3_response)
|
1047
|
+
expect(client.dialect).to eq '0x0300'
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
it 'returns the string \'SMB2\'' do
|
1051
|
+
expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
context 'when the response contains the SMB2 wildcard revision number dialect' do
|
1056
|
+
it 'only turns off SMB1 support' do
|
1057
|
+
smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
|
1058
|
+
client.parse_negotiate_response(smb2_response)
|
1059
|
+
expect(client.smb1).to be false
|
1060
|
+
expect(client.smb2).to be true
|
1061
|
+
expect(client.smb3).to be true
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
context 'when the negotiation failed' do
|
1066
|
+
context 'with a STATUS_NOT_SUPPORTED status code' do
|
1067
|
+
before :example do
|
1068
|
+
error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
it 'raises the expected exception with SMB2' do
|
1072
|
+
expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1073
|
+
RubySMB::Error::NegotiationFailure,
|
1074
|
+
'Unable to negotiate with remote host, SMB2 not supported'
|
1075
|
+
)
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
it 'raises the expected exception with SMB3' do
|
1079
|
+
expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1080
|
+
RubySMB::Error::NegotiationFailure,
|
1081
|
+
'Unable to negotiate with remote host, SMB3 not supported'
|
1082
|
+
)
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
context 'with an unknown status code' do
|
1087
|
+
it 'raises the expected exception' do
|
1088
|
+
expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
|
1089
|
+
RubySMB::Error::NegotiationFailure,
|
1090
|
+
'Unable to negotiate with remote host'
|
1091
|
+
)
|
1092
|
+
end
|
1093
|
+
end
|
1094
|
+
end
|
529
1095
|
end
|
530
1096
|
|
531
1097
|
describe '#negotiate' do
|
1098
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1099
|
+
before :example do
|
1100
|
+
allow(client).to receive(:negotiate_request)
|
1101
|
+
allow(client).to receive(:send_recv)
|
1102
|
+
allow(client).to receive(:negotiate_response)
|
1103
|
+
allow(client).to receive(:parse_negotiate_response)
|
1104
|
+
end
|
1105
|
+
|
532
1106
|
it 'calls the backing methods' do
|
533
1107
|
expect(client).to receive(:negotiate_request)
|
534
1108
|
expect(client).to receive(:send_recv)
|
@@ -537,18 +1111,264 @@ RSpec.describe RubySMB::Client do
|
|
537
1111
|
client.negotiate
|
538
1112
|
end
|
539
1113
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
1114
|
+
context 'with SMB1' do
|
1115
|
+
it 'sets the response-packet #dialects array with the dialects sent in the request' do
|
1116
|
+
request_packet = client.smb1_negotiate_request
|
1117
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1118
|
+
allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
|
1119
|
+
expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
|
1120
|
+
client.negotiate
|
1121
|
+
end
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
['0x0300', '0x0302'].each do |dialect|
|
1125
|
+
context "with #{dialect} dialect" do
|
1126
|
+
before :example do
|
1127
|
+
client.dialect = dialect
|
1128
|
+
end
|
1129
|
+
|
1130
|
+
it 'sets the expected encryption algorithm' do
|
1131
|
+
client.negotiate
|
1132
|
+
expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
context "with 0x0311 dialect" do
|
1138
|
+
it 'calls #parse_smb3_encryption_data' do
|
1139
|
+
client.dialect = '0x0311'
|
1140
|
+
request_packet = client.smb2_3_negotiate_request
|
1141
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1142
|
+
allow(client).to receive(:negotiate_response).and_return(smb3_response)
|
1143
|
+
expect(client).to receive(:parse_smb3_encryption_data).with(request_packet, smb3_response)
|
1144
|
+
client.negotiate
|
1145
|
+
end
|
547
1146
|
end
|
548
1147
|
|
549
|
-
|
550
|
-
|
551
|
-
|
1148
|
+
context 'with a wildcard revision number response' do
|
1149
|
+
before :example do
|
1150
|
+
client.dialect = '0x02ff'
|
1151
|
+
allow(client).to receive(:smb2_message_id=) do
|
1152
|
+
client.dialect = '0x0202'
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
it 'increments the message ID' do
|
1157
|
+
expect(client).to receive(:smb2_message_id=).with(1)
|
1158
|
+
client.negotiate
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
it 're-negotiates' do
|
1162
|
+
expect(client).to receive(:negotiate_request).twice
|
1163
|
+
expect(client).to receive(:send_recv).twice
|
1164
|
+
expect(client).to receive(:negotiate_response).twice
|
1165
|
+
expect(client).to receive(:parse_negotiate_response).twice
|
1166
|
+
client.negotiate
|
1167
|
+
end
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
context 'when an error occurs' do
|
1171
|
+
before :example do
|
1172
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1173
|
+
allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
|
1174
|
+
client.smb1 = false
|
1175
|
+
client.smb2 = false
|
1176
|
+
client.smb3 = false
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
context 'with SMB1' do
|
1180
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1181
|
+
|
1182
|
+
it 'raise the expected exception' do
|
1183
|
+
client.smb1 = true
|
1184
|
+
expect { client.negotiate }.to raise_error(
|
1185
|
+
RubySMB::Error::NegotiationFailure,
|
1186
|
+
"Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
|
1187
|
+
)
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
context 'with SMB2' do
|
1192
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1193
|
+
|
1194
|
+
it 'raise the expected exception' do
|
1195
|
+
client.smb2 = true
|
1196
|
+
expect { client.negotiate }.to raise_error(
|
1197
|
+
RubySMB::Error::NegotiationFailure,
|
1198
|
+
"Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
|
1199
|
+
)
|
1200
|
+
end
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
context 'with SMB3' do
|
1204
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1205
|
+
|
1206
|
+
it 'raise the expected exception' do
|
1207
|
+
client.smb3 = true
|
1208
|
+
expect { client.negotiate }.to raise_error(
|
1209
|
+
RubySMB::Error::NegotiationFailure,
|
1210
|
+
"Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
|
1211
|
+
)
|
1212
|
+
end
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
describe '#parse_smb3_encryption_data' do
|
1217
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1218
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
|
1219
|
+
let(:nc_encryption) do
|
1220
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1221
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1222
|
+
)
|
1223
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
1224
|
+
nc
|
1225
|
+
end
|
1226
|
+
let(:nc_integrity) do
|
1227
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1228
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1229
|
+
)
|
1230
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
1231
|
+
nc
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
before :example do
|
1235
|
+
allow(smb3_client).to receive(:update_preauth_hash)
|
1236
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1237
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
context 'when selecting the integrity hash algorithm' do
|
1241
|
+
context 'with one algorithm' do
|
1242
|
+
it 'selects the expected algorithm' do
|
1243
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1244
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1245
|
+
end
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
context 'with multiple algorithms' do
|
1249
|
+
it 'selects the first algorithm' do
|
1250
|
+
nc = smb3_response.find_negotiate_context(
|
1251
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1252
|
+
)
|
1253
|
+
nc.data.hash_algorithms << 3
|
1254
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1255
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1256
|
+
end
|
1257
|
+
end
|
1258
|
+
|
1259
|
+
context 'without integrity negotiate context' do
|
1260
|
+
it 'raises the expected exception' do
|
1261
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1262
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1263
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1264
|
+
RubySMB::Error::EncryptionError,
|
1265
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1266
|
+
)
|
1267
|
+
end
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
context 'with an unknown integrity hash algorithm' do
|
1271
|
+
it 'raises the expected exception' do
|
1272
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1273
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1274
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1275
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1276
|
+
)
|
1277
|
+
nc.data.hash_algorithms << 5
|
1278
|
+
smb3_response.add_negotiate_context(nc)
|
1279
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1280
|
+
RubySMB::Error::EncryptionError,
|
1281
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1282
|
+
)
|
1283
|
+
end
|
1284
|
+
end
|
1285
|
+
end
|
1286
|
+
|
1287
|
+
context 'when selecting the encryption algorithm' do
|
1288
|
+
context 'with one algorithm' do
|
1289
|
+
it 'selects the expected algorithm' do
|
1290
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1291
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1292
|
+
end
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
context 'with multiple algorithms' do
|
1296
|
+
it 'selects the AES-128-GCM algorithm if included' do
|
1297
|
+
nc = smb3_response.find_negotiate_context(
|
1298
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1299
|
+
)
|
1300
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1301
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1302
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
it 'selects the first algorithm if AES-128-GCM is not included' do
|
1306
|
+
nc = smb3_response.find_negotiate_context(
|
1307
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1308
|
+
)
|
1309
|
+
nc.data.ciphers << 3
|
1310
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1311
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
it 'keep tracks of the server supported algorithms' do
|
1315
|
+
nc = smb3_response.find_negotiate_context(
|
1316
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1317
|
+
)
|
1318
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1319
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1320
|
+
expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
|
1321
|
+
end
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
context 'without encryption context' do
|
1325
|
+
it 'raises the expected exception' do
|
1326
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1327
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1328
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1329
|
+
RubySMB::Error::EncryptionError,
|
1330
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1331
|
+
)
|
1332
|
+
end
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
context 'with an unknown encryption algorithm' do
|
1336
|
+
it 'raises the expected exception' do
|
1337
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1338
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1339
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1340
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1341
|
+
)
|
1342
|
+
nc.data.ciphers << 14
|
1343
|
+
smb3_response.add_negotiate_context(nc)
|
1344
|
+
expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
|
1345
|
+
RubySMB::Error::EncryptionError,
|
1346
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1347
|
+
)
|
1348
|
+
end
|
1349
|
+
end
|
1350
|
+
end
|
1351
|
+
|
1352
|
+
context 'when selecting the compression algorithm' do
|
1353
|
+
it 'keep tracks of the server supported algorithms' do
|
1354
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1355
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
1356
|
+
)
|
1357
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
1358
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
1359
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
1360
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
1361
|
+
smb3_response.add_negotiate_context(nc)
|
1362
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1363
|
+
expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
|
1364
|
+
end
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
it 'updates the preauth hash' do
|
1368
|
+
expect(smb3_client).to receive(:update_preauth_hash).with(request_packet)
|
1369
|
+
expect(smb3_client).to receive(:update_preauth_hash).with(smb3_response)
|
1370
|
+
smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
|
1371
|
+
end
|
552
1372
|
end
|
553
1373
|
end
|
554
1374
|
end
|
@@ -875,6 +1695,39 @@ RSpec.describe RubySMB::Client do
|
|
875
1695
|
smb2_client.smb2_authenticate
|
876
1696
|
expect(smb2_client.os_version).to eq '6.1.7601'
|
877
1697
|
end
|
1698
|
+
|
1699
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1700
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1701
|
+
smb2_client.dialect = dialect
|
1702
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1703
|
+
smb2_client.smb2_authenticate
|
1704
|
+
end
|
1705
|
+
end
|
1706
|
+
|
1707
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1708
|
+
smb2_client.dialect = '0x0311'
|
1709
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
|
1710
|
+
smb2_client.smb2_authenticate
|
1711
|
+
end
|
1712
|
+
|
1713
|
+
context 'when setting the encryption_required parameter' do
|
1714
|
+
before :example do
|
1715
|
+
smb2_client.smb3 = true
|
1716
|
+
smb2_client.encryption_required = false
|
1717
|
+
end
|
1718
|
+
|
1719
|
+
it 'sets the encryption_required parameter to true if the server requires encryption' do
|
1720
|
+
final_response_packet.session_flags.encrypt_data = 1
|
1721
|
+
smb2_client.smb2_authenticate
|
1722
|
+
expect(smb2_client.encryption_required).to be true
|
1723
|
+
end
|
1724
|
+
|
1725
|
+
it 'does not set the encryption_required parameter if the server does not require encryption' do
|
1726
|
+
final_response_packet.session_flags.encrypt_data = 0
|
1727
|
+
smb2_client.smb2_authenticate
|
1728
|
+
expect(smb2_client.encryption_required).to be false
|
1729
|
+
end
|
1730
|
+
end
|
878
1731
|
end
|
879
1732
|
|
880
1733
|
describe '#smb2_ntlmssp_negotiate_packet' do
|
@@ -890,20 +1743,34 @@ RSpec.describe RubySMB::Client do
|
|
890
1743
|
smb2_client.smb2_ntlmssp_negotiate_packet
|
891
1744
|
end
|
892
1745
|
|
893
|
-
it '
|
894
|
-
expect(smb2_client.smb2_ntlmssp_negotiate_packet.
|
895
|
-
end
|
896
|
-
|
897
|
-
it 'increments client#smb2_message_id' do
|
898
|
-
expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
|
1746
|
+
it 'enables signing' do
|
1747
|
+
expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
|
899
1748
|
end
|
900
1749
|
end
|
901
1750
|
|
902
1751
|
describe '#smb2_ntlmssp_negotiate' do
|
1752
|
+
before :example do
|
1753
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
|
1754
|
+
allow(smb2_client).to receive(:send_recv)
|
1755
|
+
end
|
1756
|
+
|
903
1757
|
it 'sends the request packet and receives a response' do
|
904
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
905
|
-
expect(
|
906
|
-
|
1758
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
1759
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1760
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1761
|
+
end
|
1762
|
+
|
1763
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1764
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1765
|
+
smb2_client.dialect = dialect
|
1766
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1767
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1768
|
+
end
|
1769
|
+
end
|
1770
|
+
|
1771
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1772
|
+
smb2_client.dialect = '0x0311'
|
1773
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
907
1774
|
smb2_client.smb2_ntlmssp_negotiate
|
908
1775
|
end
|
909
1776
|
end
|
@@ -961,13 +1828,35 @@ RSpec.describe RubySMB::Client do
|
|
961
1828
|
it 'sets the session ID on the request packet' do
|
962
1829
|
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
|
963
1830
|
end
|
1831
|
+
|
1832
|
+
it 'enables signing' do
|
1833
|
+
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
|
1834
|
+
end
|
964
1835
|
end
|
965
1836
|
|
966
1837
|
describe '#smb2_ntlmssp_authenticate' do
|
1838
|
+
before :example do
|
1839
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
|
1840
|
+
allow(smb2_client).to receive(:send_recv)
|
1841
|
+
end
|
1842
|
+
|
967
1843
|
it 'sends the request packet and receives a response' do
|
968
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
969
|
-
expect(
|
970
|
-
|
1844
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
1845
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1846
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1847
|
+
end
|
1848
|
+
|
1849
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1850
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1851
|
+
smb2_client.dialect = dialect
|
1852
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1853
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1854
|
+
end
|
1855
|
+
end
|
1856
|
+
|
1857
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1858
|
+
smb2_client.dialect = '0x0311'
|
1859
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
971
1860
|
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
972
1861
|
end
|
973
1862
|
end
|
@@ -1103,6 +1992,108 @@ RSpec.describe RubySMB::Client do
|
|
1103
1992
|
end
|
1104
1993
|
end
|
1105
1994
|
end
|
1995
|
+
|
1996
|
+
describe '#smb3_sign' do
|
1997
|
+
context 'if signing is required and we have a session key' do
|
1998
|
+
let(:request) {
|
1999
|
+
packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
2000
|
+
packet.smb2_header.flags.signed = 1
|
2001
|
+
packet.smb2_header.signature = "\x00" * 16
|
2002
|
+
packet
|
2003
|
+
}
|
2004
|
+
let(:session_key) { 'Session Key' }
|
2005
|
+
before :example do
|
2006
|
+
smb3_client.session_key = session_key
|
2007
|
+
smb3_client.signing_required = true
|
2008
|
+
end
|
2009
|
+
|
2010
|
+
['0x0300', '0x0302'].each do |dialect|
|
2011
|
+
context "with #{dialect} dialect" do
|
2012
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2013
|
+
smb3_client.dialect = dialect
|
2014
|
+
fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
|
2015
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2016
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2017
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2018
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2019
|
+
end
|
2020
|
+
end
|
2021
|
+
end
|
2022
|
+
|
2023
|
+
context "with 0x0311 dialect" do
|
2024
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2025
|
+
smb3_client.dialect = '0x0311'
|
2026
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2027
|
+
fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
|
2028
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2029
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2030
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2031
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2032
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2033
|
+
end
|
2034
|
+
end
|
2035
|
+
|
2036
|
+
context 'with an incompatible dialect' do
|
2037
|
+
it 'raises the expected exception' do
|
2038
|
+
smb3_client.dialect = '0x0202'
|
2039
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2040
|
+
RubySMB::Error::SigningError,
|
2041
|
+
'Dialect is incompatible with SMBv3 signing'
|
2042
|
+
)
|
2043
|
+
end
|
2044
|
+
end
|
2045
|
+
end
|
2046
|
+
|
2047
|
+
context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
|
2048
|
+
let(:request) {
|
2049
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
2050
|
+
packet.smb2_header.flags.signed = 1
|
2051
|
+
packet.smb2_header.signature = "\x00" * 16
|
2052
|
+
packet
|
2053
|
+
}
|
2054
|
+
let(:session_key) { 'Session Key' }
|
2055
|
+
before :example do
|
2056
|
+
smb3_client.session_key = session_key
|
2057
|
+
smb3_client.signing_required = false
|
2058
|
+
end
|
2059
|
+
|
2060
|
+
['0x0300', '0x0302'].each do |dialect|
|
2061
|
+
context "with #{dialect} dialect" do
|
2062
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2063
|
+
smb3_client.dialect = dialect
|
2064
|
+
fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
|
2065
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2066
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2067
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2068
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2069
|
+
end
|
2070
|
+
end
|
2071
|
+
end
|
2072
|
+
|
2073
|
+
context "with 0x0311 dialect" do
|
2074
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2075
|
+
smb3_client.dialect = '0x0311'
|
2076
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2077
|
+
fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
|
2078
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2079
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2080
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2081
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2082
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2083
|
+
end
|
2084
|
+
end
|
2085
|
+
|
2086
|
+
context 'with an incompatible dialect' do
|
2087
|
+
it 'raises the expected exception' do
|
2088
|
+
smb3_client.dialect = '0x0202'
|
2089
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2090
|
+
RubySMB::Error::SigningError,
|
2091
|
+
'Dialect is incompatible with SMBv3 signing'
|
2092
|
+
)
|
2093
|
+
end
|
2094
|
+
end
|
2095
|
+
end
|
2096
|
+
end
|
1106
2097
|
end
|
1107
2098
|
|
1108
2099
|
context '#increment_smb_message_id' do
|
@@ -1156,7 +2147,10 @@ RSpec.describe RubySMB::Client do
|
|
1156
2147
|
|
1157
2148
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1158
2149
|
response.smb_header.nt_status = 0xc0000015
|
1159
|
-
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2150
|
+
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2151
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2152
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2153
|
+
)
|
1160
2154
|
end
|
1161
2155
|
|
1162
2156
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
@@ -1177,11 +2171,14 @@ RSpec.describe RubySMB::Client do
|
|
1177
2171
|
}
|
1178
2172
|
|
1179
2173
|
describe '#smb2_tree_connect' do
|
1180
|
-
it 'builds and sends
|
2174
|
+
it 'builds and sends the expected TreeconnectRequest for the supplied share' do
|
1181
2175
|
allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
2176
|
+
expect(smb2_client).to receive(:send_recv) do |req|
|
2177
|
+
expect(req).to eq(request)
|
2178
|
+
expect(req.smb2_header.tree_id).to eq(65_535)
|
2179
|
+
expect(req.path).to eq(path.encode('UTF-16LE'))
|
2180
|
+
response.to_binary_s
|
2181
|
+
end
|
1185
2182
|
smb2_client.smb2_tree_connect(path)
|
1186
2183
|
end
|
1187
2184
|
|
@@ -1200,11 +2197,20 @@ RSpec.describe RubySMB::Client do
|
|
1200
2197
|
|
1201
2198
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1202
2199
|
response.smb2_header.nt_status = 0xc0000015
|
1203
|
-
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2200
|
+
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2201
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2202
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2203
|
+
)
|
1204
2204
|
end
|
1205
2205
|
|
1206
2206
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
1207
|
-
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
|
2207
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
|
2208
|
+
smb2_client.smb2_tree_from_response(path, response)
|
2209
|
+
end
|
2210
|
+
|
2211
|
+
it 'creates a new with encryption set if the response requires it' do
|
2212
|
+
response.share_flags.encrypt = 1
|
2213
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
|
1208
2214
|
smb2_client.smb2_tree_from_response(path, response)
|
1209
2215
|
end
|
1210
2216
|
end
|
@@ -1301,7 +2307,7 @@ RSpec.describe RubySMB::Client do
|
|
1301
2307
|
end
|
1302
2308
|
|
1303
2309
|
it 'raise an InvalidPacket exception when the response is not valid' do
|
1304
|
-
echo_response.smb_header.command = RubySMB::SMB1::Commands::
|
2310
|
+
echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
|
1305
2311
|
allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
1306
2312
|
expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
|
1307
2313
|
end
|
@@ -1473,5 +2479,198 @@ RSpec.describe RubySMB::Client do
|
|
1473
2479
|
end
|
1474
2480
|
end
|
1475
2481
|
end
|
2482
|
+
|
2483
|
+
describe '#update_preauth_hash' do
|
2484
|
+
it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
|
2485
|
+
expect { client.update_preauth_hash('Test') }.to raise_error(
|
2486
|
+
RubySMB::Error::EncryptionError,
|
2487
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
2488
|
+
)
|
2489
|
+
end
|
2490
|
+
|
2491
|
+
it 'computes the hash value' do
|
2492
|
+
packet = RubySMB::SMB2::Packet::EchoRequest.new
|
2493
|
+
data = 'Previous hash'
|
2494
|
+
algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
|
2495
|
+
RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
2496
|
+
]
|
2497
|
+
client.preauth_integrity_hash_algorithm = algo
|
2498
|
+
client.preauth_integrity_hash_value = data
|
2499
|
+
hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
|
2500
|
+
client.update_preauth_hash(packet)
|
2501
|
+
expect(client.preauth_integrity_hash_value).to eq(hash)
|
2502
|
+
end
|
2503
|
+
end
|
2504
|
+
|
2505
|
+
context 'Encryption' do
|
2506
|
+
describe '#smb3_encrypt' do
|
2507
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2508
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2509
|
+
let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
|
2510
|
+
|
2511
|
+
before :example do
|
2512
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
|
2513
|
+
allow(transform_packet).to receive(:encrypt)
|
2514
|
+
client.session_key = session_key
|
2515
|
+
end
|
2516
|
+
|
2517
|
+
it 'does not generate a new client encryption key if it already exists' do
|
2518
|
+
client.client_encryption_key = 'key'
|
2519
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2520
|
+
expect(client.client_encryption_key).to eq('key')
|
2521
|
+
client.smb3_encrypt(data)
|
2522
|
+
end
|
2523
|
+
|
2524
|
+
['0x0300', '0x0302'].each do |dialect|
|
2525
|
+
context "with #{dialect} dialect" do
|
2526
|
+
before :example do
|
2527
|
+
client.dialect = dialect
|
2528
|
+
end
|
2529
|
+
|
2530
|
+
it 'generates the client encryption key with the expected parameters' do
|
2531
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2532
|
+
session_key,
|
2533
|
+
"SMB2AESCCM\x00",
|
2534
|
+
"ServerIn \x00"
|
2535
|
+
).and_call_original
|
2536
|
+
client.smb3_encrypt(data)
|
2537
|
+
end
|
2538
|
+
end
|
2539
|
+
end
|
2540
|
+
|
2541
|
+
context 'with 0x0311 dialect' do
|
2542
|
+
it 'generates the client encryption key with the expected parameters' do
|
2543
|
+
client.preauth_integrity_hash_value = ''
|
2544
|
+
client.dialect = '0x0311'
|
2545
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2546
|
+
session_key,
|
2547
|
+
"SMBC2SCipherKey\x00",
|
2548
|
+
''
|
2549
|
+
).and_call_original
|
2550
|
+
client.smb3_encrypt(data)
|
2551
|
+
end
|
2552
|
+
end
|
2553
|
+
|
2554
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2555
|
+
client.dialect = '0x0202'
|
2556
|
+
expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
|
2557
|
+
end
|
2558
|
+
|
2559
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2560
|
+
client.dialect = '0x0300'
|
2561
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2562
|
+
client.session_id = 123
|
2563
|
+
client.smb3_encrypt(data)
|
2564
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
|
2565
|
+
expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
|
2566
|
+
end
|
2567
|
+
|
2568
|
+
it 'generates the expected client encryption key with 0x0302 dialect' do
|
2569
|
+
client.dialect = '0x0302'
|
2570
|
+
expected_enc_key =
|
2571
|
+
"\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
|
2572
|
+
client.smb3_encrypt(data)
|
2573
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2574
|
+
end
|
2575
|
+
|
2576
|
+
it 'generates the expected client encryption key with 0x0311 dialect' do
|
2577
|
+
client.dialect = '0x0311'
|
2578
|
+
client.session_key =
|
2579
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2580
|
+
client.preauth_integrity_hash_value =
|
2581
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2582
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2583
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2584
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2585
|
+
expected_enc_key =
|
2586
|
+
"\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
|
2587
|
+
client.smb3_encrypt(data)
|
2588
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2589
|
+
end
|
2590
|
+
end
|
2591
|
+
|
2592
|
+
describe '#smb3_decrypt' do
|
2593
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2594
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2595
|
+
|
2596
|
+
before :example do
|
2597
|
+
allow(transform_packet).to receive(:decrypt)
|
2598
|
+
client.session_key = session_key
|
2599
|
+
end
|
2600
|
+
|
2601
|
+
it 'does not generate a new server encryption key if it already exists' do
|
2602
|
+
client.server_encryption_key = 'key'
|
2603
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2604
|
+
expect(client.server_encryption_key).to eq('key')
|
2605
|
+
client.smb3_decrypt(transform_packet)
|
2606
|
+
end
|
2607
|
+
|
2608
|
+
['0x0300', '0x0302'].each do |dialect|
|
2609
|
+
context "with #{dialect} dialect" do
|
2610
|
+
before :example do
|
2611
|
+
client.dialect = dialect
|
2612
|
+
end
|
2613
|
+
|
2614
|
+
it 'generates the client encryption key with the expected parameters' do
|
2615
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2616
|
+
session_key,
|
2617
|
+
"SMB2AESCCM\x00",
|
2618
|
+
"ServerOut\x00"
|
2619
|
+
).and_call_original
|
2620
|
+
client.smb3_decrypt(transform_packet)
|
2621
|
+
end
|
2622
|
+
end
|
2623
|
+
end
|
2624
|
+
|
2625
|
+
context 'with 0x0311 dialect' do
|
2626
|
+
it 'generates the client encryption key with the expected parameters' do
|
2627
|
+
client.preauth_integrity_hash_value = ''
|
2628
|
+
client.dialect = '0x0311'
|
2629
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2630
|
+
session_key,
|
2631
|
+
"SMBS2CCipherKey\x00",
|
2632
|
+
''
|
2633
|
+
).and_call_original
|
2634
|
+
client.smb3_decrypt(transform_packet)
|
2635
|
+
end
|
2636
|
+
end
|
2637
|
+
|
2638
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2639
|
+
client.dialect = '0x0202'
|
2640
|
+
expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
|
2641
|
+
end
|
2642
|
+
|
2643
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2644
|
+
client.dialect = '0x0300'
|
2645
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2646
|
+
client.session_id = 123
|
2647
|
+
client.smb3_decrypt(transform_packet)
|
2648
|
+
expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
|
2649
|
+
end
|
2650
|
+
|
2651
|
+
it 'generates the expected server encryption key with 0x0302 dialect' do
|
2652
|
+
client.dialect = '0x0302'
|
2653
|
+
expected_enc_key =
|
2654
|
+
"\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
|
2655
|
+
client.smb3_decrypt(transform_packet)
|
2656
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2657
|
+
end
|
2658
|
+
|
2659
|
+
it 'generates the expected server encryption key with 0x0311 dialect' do
|
2660
|
+
client.dialect = '0x0311'
|
2661
|
+
client.session_key =
|
2662
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2663
|
+
client.preauth_integrity_hash_value =
|
2664
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2665
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2666
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2667
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2668
|
+
expected_enc_key =
|
2669
|
+
"\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
|
2670
|
+
client.smb3_decrypt(transform_packet)
|
2671
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2672
|
+
end
|
2673
|
+
end
|
2674
|
+
end
|
1476
2675
|
end
|
1477
2676
|
|