ruby_smb 1.0.4 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.travis.yml +3 -2
- data/Gemfile +6 -2
- data/README.md +35 -47
- data/examples/enum_registry_key.rb +28 -0
- data/examples/enum_registry_values.rb +30 -0
- data/examples/negotiate.rb +51 -8
- data/examples/pipes.rb +2 -1
- data/examples/read_file_encryption.rb +56 -0
- data/examples/read_registry_key_value.rb +32 -0
- data/lib/ruby_smb.rb +4 -1
- data/lib/ruby_smb/client.rb +207 -18
- 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 +153 -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/client/winreg.rb +46 -0
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dcerpc.rb +38 -0
- data/lib/ruby_smb/dcerpc/bind.rb +2 -2
- data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
- data/lib/ruby_smb/dcerpc/error.rb +3 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
- data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
- data/lib/ruby_smb/dcerpc/request.rb +28 -9
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
- data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
- data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
- data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
- data/lib/ruby_smb/dispatcher/socket.rb +4 -3
- data/lib/ruby_smb/error.rb +28 -1
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +6 -4
- data/lib/ruby_smb/smb1/packet/empty_packet.rb +4 -2
- 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 +79 -3
- data/lib/ruby_smb/smb1/tree.rb +12 -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/error_packet.rb +9 -4
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
- 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 +77 -3
- 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 +5 -3
- data/spec/lib/ruby_smb/client_spec.rb +1441 -61
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
- data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
- data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
- data/spec/lib/ruby_smb/error_spec.rb +59 -0
- data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
- data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +10 -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/smb1/pipe_spec.rb +210 -148
- 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/error_packet_spec.rb +29 -2
- 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 +220 -149
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
- metadata +187 -81
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
- data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -5,6 +5,7 @@ RSpec.describe RubySMB::SMB2::BitField::SessionFlags do
|
|
5
5
|
|
6
6
|
it { is_expected.to respond_to :guest }
|
7
7
|
it { is_expected.to respond_to :null }
|
8
|
+
it { is_expected.to respond_to :encrypt_data }
|
8
9
|
|
9
10
|
it 'is little endian' do
|
10
11
|
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
@@ -25,4 +26,12 @@ RSpec.describe RubySMB::SMB2::BitField::SessionFlags do
|
|
25
26
|
|
26
27
|
it_behaves_like 'bit field with one flag set', :null, 'v', 0x00000002
|
27
28
|
end
|
29
|
+
|
30
|
+
describe '#encrypt_data' do
|
31
|
+
it 'should be a 1-bit field per the SMB spec' do
|
32
|
+
expect(flags.encrypt_data).to be_a BinData::Bit1
|
33
|
+
end
|
34
|
+
|
35
|
+
it_behaves_like 'bit field with one flag set', :encrypt_data, 'v', 0x00000004
|
36
|
+
end
|
28
37
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
RSpec.describe RubySMB::SMB2::BitField::ShareFlags do
|
2
2
|
subject(:flags) { described_class.new }
|
3
3
|
|
4
|
+
it { is_expected.to respond_to :vdo_caching }
|
5
|
+
it { is_expected.to respond_to :auto_caching }
|
4
6
|
it { is_expected.to respond_to :dfs_root }
|
5
7
|
it { is_expected.to respond_to :dfs }
|
6
8
|
it { is_expected.to respond_to :encrypt }
|
@@ -11,11 +13,28 @@ RSpec.describe RubySMB::SMB2::BitField::ShareFlags do
|
|
11
13
|
it { is_expected.to respond_to :namespace_caching }
|
12
14
|
it { is_expected.to respond_to :shared_delete }
|
13
15
|
it { is_expected.to respond_to :restrict_exclusive_opens }
|
16
|
+
it { is_expected.to respond_to :identity_remoting }
|
14
17
|
|
15
18
|
it 'is little endian' do
|
16
19
|
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
17
20
|
end
|
18
21
|
|
22
|
+
describe '#vdo_caching' do
|
23
|
+
it 'should be a 1-bit field per the SMB spec' do
|
24
|
+
expect(flags.vdo_caching).to be_a BinData::Bit1
|
25
|
+
end
|
26
|
+
|
27
|
+
it_behaves_like 'bit field with one flag set', :vdo_caching, 'V', 0x00000020
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#auto_caching' do
|
31
|
+
it 'should be a 1-bit field per the SMB spec' do
|
32
|
+
expect(flags.auto_caching).to be_a BinData::Bit1
|
33
|
+
end
|
34
|
+
|
35
|
+
it_behaves_like 'bit field with one flag set', :auto_caching, 'V', 0x00000010
|
36
|
+
end
|
37
|
+
|
19
38
|
describe '#dfs' do
|
20
39
|
it 'should be a 1-bit field per the SMB spec' do
|
21
40
|
expect(flags.dfs).to be_a BinData::Bit1
|
@@ -96,6 +115,14 @@ RSpec.describe RubySMB::SMB2::BitField::ShareFlags do
|
|
96
115
|
it_behaves_like 'bit field with one flag set', :encrypt, 'V', 0x00008000
|
97
116
|
end
|
98
117
|
|
118
|
+
describe '#identity_remoting' do
|
119
|
+
it 'should be a 1-bit field per the SMB spec' do
|
120
|
+
expect(flags.identity_remoting).to be_a BinData::Bit1
|
121
|
+
end
|
122
|
+
|
123
|
+
it_behaves_like 'bit field with one flag set', :identity_remoting, 'V', 0x00040000
|
124
|
+
end
|
125
|
+
|
99
126
|
describe '#set_manual_caching' do
|
100
127
|
it 'turns off the caching bits' do
|
101
128
|
flags.set_manual_caching
|
@@ -43,6 +43,7 @@ RSpec.describe RubySMB::SMB2::File do
|
|
43
43
|
it { is_expected.to respond_to :size }
|
44
44
|
it { is_expected.to respond_to :size_on_disk }
|
45
45
|
it { is_expected.to respond_to :tree }
|
46
|
+
it { is_expected.to respond_to :tree_connect_encrypt_data }
|
46
47
|
|
47
48
|
it 'pulls the attributes from the response packet' do
|
48
49
|
expect(file.attributes).to eq create_response.file_attributes
|
@@ -52,10 +53,18 @@ RSpec.describe RubySMB::SMB2::File do
|
|
52
53
|
expect(file.guid).to eq create_response.file_id
|
53
54
|
end
|
54
55
|
|
55
|
-
it 'pulls the timestamps from the response packet' do
|
56
|
+
it 'pulls the last access timestamps from the response packet' do
|
56
57
|
expect(file.last_access).to eq create_response.last_access.to_datetime
|
57
58
|
end
|
58
59
|
|
60
|
+
it 'pulls the last change timestamps from the response packet' do
|
61
|
+
expect(file.last_change).to eq create_response.last_change.to_datetime
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'pulls the last write timestamps from the response packet' do
|
65
|
+
expect(file.last_write).to eq create_response.last_write.to_datetime
|
66
|
+
end
|
67
|
+
|
59
68
|
it 'pulls the size from the response packet' do
|
60
69
|
expect(file.size).to eq create_response.end_of_file
|
61
70
|
end
|
@@ -64,6 +73,10 @@ RSpec.describe RubySMB::SMB2::File do
|
|
64
73
|
expect(file.size_on_disk).to eq create_response.allocation_size
|
65
74
|
end
|
66
75
|
|
76
|
+
it 'sets the tree_connect_encrypt_data flag to false by default' do
|
77
|
+
expect(file.tree_connect_encrypt_data).to be false
|
78
|
+
end
|
79
|
+
|
67
80
|
describe '#set_header_fields' do
|
68
81
|
let(:request) { RubySMB::SMB2::Packet::ReadRequest.new }
|
69
82
|
it 'calls the set_header_field method from the Tree' do
|
@@ -113,11 +126,17 @@ RSpec.describe RubySMB::SMB2::File do
|
|
113
126
|
|
114
127
|
it 'uses a single packet to read the entire file' do
|
115
128
|
expect(file).to receive(:read_packet).with(read_length: 108, offset: 0).and_return(small_read)
|
116
|
-
expect(client).to receive(:send_recv).with(small_read).and_return 'fake data'
|
129
|
+
expect(client).to receive(:send_recv).with(small_read, encrypt: false).and_return 'fake data'
|
117
130
|
expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).with('fake data').and_return(small_response)
|
118
131
|
expect(file.read).to eq 'fake data'
|
119
132
|
end
|
120
133
|
|
134
|
+
it 'calls Client #send_recv with encryption set if required' do
|
135
|
+
file.tree_connect_encrypt_data = true
|
136
|
+
expect(client).to receive(:send_recv).with(small_read, encrypt: true)
|
137
|
+
file.read
|
138
|
+
end
|
139
|
+
|
121
140
|
context 'when the response is not valid' do
|
122
141
|
it 'raise an InvalidPacket exception' do
|
123
142
|
small_response.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
|
@@ -155,6 +174,14 @@ RSpec.describe RubySMB::SMB2::File do
|
|
155
174
|
file.read(bytes: (described_class::MAX_PACKET_SIZE * 2))
|
156
175
|
end
|
157
176
|
|
177
|
+
it 'calls Client #send_recv with encryption set if required' do
|
178
|
+
read_request = double('Read Request')
|
179
|
+
allow(file).to receive(:read_packet).and_return(read_request)
|
180
|
+
file.tree_connect_encrypt_data = true
|
181
|
+
expect(client).to receive(:send_recv).twice.with(read_request, encrypt: true)
|
182
|
+
file.read(bytes: (described_class::MAX_PACKET_SIZE * 2))
|
183
|
+
end
|
184
|
+
|
158
185
|
context 'when the second response is not valid' do
|
159
186
|
it 'raise an InvalidPacket exception' do
|
160
187
|
allow(file).to receive(:read_packet).with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE) do
|
@@ -204,6 +231,14 @@ RSpec.describe RubySMB::SMB2::File do
|
|
204
231
|
expect(client).to receive(:send_recv).once.and_return(write_response.to_binary_s)
|
205
232
|
file.write(data: 'test')
|
206
233
|
end
|
234
|
+
|
235
|
+
it 'calls Client #send_recv with encryption set if required' do
|
236
|
+
write_request = double('Write Request')
|
237
|
+
allow(file).to receive(:write_packet).and_return(write_request)
|
238
|
+
file.tree_connect_encrypt_data = true
|
239
|
+
expect(client).to receive(:send_recv).once.with(write_request, encrypt: true).and_return(write_response.to_binary_s)
|
240
|
+
file.write(data: 'test')
|
241
|
+
end
|
207
242
|
end
|
208
243
|
|
209
244
|
context 'for a large write' do
|
@@ -211,6 +246,14 @@ RSpec.describe RubySMB::SMB2::File do
|
|
211
246
|
expect(client).to receive(:send_recv).twice.and_return(write_response.to_binary_s)
|
212
247
|
file.write(data: SecureRandom.random_bytes(described_class::MAX_PACKET_SIZE + 1))
|
213
248
|
end
|
249
|
+
|
250
|
+
it 'calls Client #send_recv with encryption set if required' do
|
251
|
+
write_request = double('Write Request')
|
252
|
+
allow(file).to receive(:write_packet).and_return(write_request)
|
253
|
+
file.tree_connect_encrypt_data = true
|
254
|
+
expect(client).to receive(:send_recv).twice.with(write_request, encrypt: true).and_return(write_response.to_binary_s)
|
255
|
+
file.write(data: SecureRandom.random_bytes(described_class::MAX_PACKET_SIZE + 1))
|
256
|
+
end
|
214
257
|
end
|
215
258
|
|
216
259
|
it 'raises an InvalidPacket exception if the response is not valid' do
|
@@ -248,7 +291,7 @@ RSpec.describe RubySMB::SMB2::File do
|
|
248
291
|
|
249
292
|
it 'uses a single packet to delete the entire file' do
|
250
293
|
expect(file).to receive(:delete_packet).and_return(small_delete)
|
251
|
-
expect(client).to receive(:send_recv).with(small_delete).and_return 'raw_response'
|
294
|
+
expect(client).to receive(:send_recv).with(small_delete, encrypt: false).and_return 'raw_response'
|
252
295
|
expect(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).with('raw_response').and_return(small_response)
|
253
296
|
expect(file.delete).to eq WindowsError::NTStatus::STATUS_SUCCESS
|
254
297
|
end
|
@@ -260,6 +303,14 @@ RSpec.describe RubySMB::SMB2::File do
|
|
260
303
|
allow(small_response).to receive(:valid?).and_return(false)
|
261
304
|
expect { file.delete }.to raise_error(RubySMB::Error::InvalidPacket)
|
262
305
|
end
|
306
|
+
|
307
|
+
it 'calls Client #send_recv with encryption set if required' do
|
308
|
+
allow(file).to receive(:delete_packet)
|
309
|
+
allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
|
310
|
+
file.tree_connect_encrypt_data = true
|
311
|
+
expect(client).to receive(:send_recv).with(small_delete, encrypt: true)
|
312
|
+
file.delete
|
313
|
+
end
|
263
314
|
end
|
264
315
|
end
|
265
316
|
|
@@ -291,11 +342,18 @@ RSpec.describe RubySMB::SMB2::File do
|
|
291
342
|
|
292
343
|
it 'uses a single packet to rename the entire file' do
|
293
344
|
expect(file).to receive(:rename_packet).and_return(small_rename)
|
294
|
-
expect(client).to receive(:send_recv).with(small_rename).and_return 'raw_response'
|
345
|
+
expect(client).to receive(:send_recv).with(small_rename, encrypt: false).and_return 'raw_response'
|
295
346
|
expect(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).with('raw_response').and_return(small_response)
|
296
347
|
expect(file.rename('new_file.txt')).to eq WindowsError::NTStatus::STATUS_SUCCESS
|
297
348
|
end
|
298
349
|
|
350
|
+
it 'calls Client #send_recv with encryption set if required' do
|
351
|
+
allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
|
352
|
+
file.tree_connect_encrypt_data = true
|
353
|
+
expect(client).to receive(:send_recv).with(small_rename, encrypt: true)
|
354
|
+
file.rename('new_file.txt')
|
355
|
+
end
|
356
|
+
|
299
357
|
it 'raises an InvalidPacket exception if the response is not valid' do
|
300
358
|
allow(client).to receive(:send_recv)
|
301
359
|
allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
|
@@ -330,7 +388,13 @@ RSpec.describe RubySMB::SMB2::File do
|
|
330
388
|
end
|
331
389
|
|
332
390
|
it 'calls Client #send_recv with the expected request' do
|
333
|
-
expect(client).to receive(:send_recv).with(request)
|
391
|
+
expect(client).to receive(:send_recv).with(request, encrypt: false)
|
392
|
+
file.close
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'calls Client #send_recv with encryption set if required' do
|
396
|
+
file.tree_connect_encrypt_data = true
|
397
|
+
expect(client).to receive(:send_recv).with(request, encrypt: true)
|
334
398
|
file.close
|
335
399
|
end
|
336
400
|
|
@@ -394,7 +458,15 @@ RSpec.describe RubySMB::SMB2::File do
|
|
394
458
|
it 'calls Client #send_recv with the expected request' do
|
395
459
|
request = double('Request')
|
396
460
|
allow(file).to receive(:read_packet).and_return(request)
|
397
|
-
expect(client).to receive(:send_recv).with(request)
|
461
|
+
expect(client).to receive(:send_recv).with(request, encrypt: false)
|
462
|
+
file.send_recv_read
|
463
|
+
end
|
464
|
+
|
465
|
+
it 'calls Client #send_recv with encryption set if required' do
|
466
|
+
request = double('Request')
|
467
|
+
allow(file).to receive(:read_packet).and_return(request)
|
468
|
+
file.tree_connect_encrypt_data = true
|
469
|
+
expect(client).to receive(:send_recv).with(request, encrypt: true)
|
398
470
|
file.send_recv_read
|
399
471
|
end
|
400
472
|
|
@@ -408,33 +480,6 @@ RSpec.describe RubySMB::SMB2::File do
|
|
408
480
|
expect { file.send_recv_read }.to raise_error(RubySMB::Error::InvalidPacket)
|
409
481
|
end
|
410
482
|
|
411
|
-
context 'when the response status code is STATUS_PENDING' do
|
412
|
-
before :example do
|
413
|
-
allow(file).to receive(:sleep)
|
414
|
-
allow(read_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_PENDING)
|
415
|
-
allow(dispatcher).to receive(:recv_packet).and_return(raw_response)
|
416
|
-
end
|
417
|
-
|
418
|
-
it 'wait 1 second and calls Client dispatcher #recv_packet method one more time' do
|
419
|
-
expect(file).to receive(:sleep).with(1)
|
420
|
-
expect(dispatcher).to receive(:recv_packet)
|
421
|
-
file.send_recv_read
|
422
|
-
end
|
423
|
-
|
424
|
-
it 'parses the response as a SMB2 ReadResponse packet' do
|
425
|
-
expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).twice.with(raw_response)
|
426
|
-
file.send_recv_read
|
427
|
-
end
|
428
|
-
|
429
|
-
it 'raises an InvalidPacket exception if the response is not valid' do
|
430
|
-
allow(dispatcher).to receive(:recv_packet) do
|
431
|
-
allow(read_response).to receive(:valid?).and_return(false)
|
432
|
-
raw_response
|
433
|
-
end
|
434
|
-
expect { file.send_recv_read }.to raise_error(RubySMB::Error::InvalidPacket)
|
435
|
-
end
|
436
|
-
end
|
437
|
-
|
438
483
|
it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS' do
|
439
484
|
allow(read_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
|
440
485
|
expect { file.send_recv_read }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
|
@@ -457,7 +502,7 @@ RSpec.describe RubySMB::SMB2::File do
|
|
457
502
|
|
458
503
|
before :example do
|
459
504
|
allow(file).to receive(:write_packet).and_return(request)
|
460
|
-
allow(client).to receive(:send_recv).and_return(raw_response)
|
505
|
+
allow(client).to receive(:send_recv).and_return(raw_response, encrypt: false)
|
461
506
|
allow(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).with(raw_response).and_return(write_response)
|
462
507
|
end
|
463
508
|
|
@@ -478,40 +523,19 @@ RSpec.describe RubySMB::SMB2::File do
|
|
478
523
|
end
|
479
524
|
|
480
525
|
it 'calls Client #send_recv with the expected request' do
|
481
|
-
expect(client).to receive(:send_recv).with(request)
|
526
|
+
expect(client).to receive(:send_recv).with(request, encrypt: false)
|
482
527
|
file.send_recv_write
|
483
528
|
end
|
484
529
|
|
485
|
-
it '
|
486
|
-
|
530
|
+
it 'calls Client #send_recv with encryption set if required' do
|
531
|
+
file.tree_connect_encrypt_data = true
|
532
|
+
expect(client).to receive(:send_recv).with(request, encrypt: true)
|
487
533
|
file.send_recv_write
|
488
534
|
end
|
489
535
|
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
allow(write_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_PENDING)
|
494
|
-
allow(dispatcher).to receive(:recv_packet).and_return(raw_response)
|
495
|
-
end
|
496
|
-
|
497
|
-
it 'wait 1 second and calls Client dispatcher #recv_packet method one more time' do
|
498
|
-
expect(file).to receive(:sleep).with(1)
|
499
|
-
expect(dispatcher).to receive(:recv_packet)
|
500
|
-
file.send_recv_write
|
501
|
-
end
|
502
|
-
|
503
|
-
it 'parses the response as a SMB2 WriteResponse packet' do
|
504
|
-
expect(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).twice.with(raw_response)
|
505
|
-
file.send_recv_write
|
506
|
-
end
|
507
|
-
|
508
|
-
it 'raises an InvalidPacket exception if the response is not valid' do
|
509
|
-
allow(dispatcher).to receive(:recv_packet) do
|
510
|
-
allow(write_response).to receive(:valid?).and_return(false)
|
511
|
-
raw_response
|
512
|
-
end
|
513
|
-
expect { file.send_recv_write }.to raise_error(RubySMB::Error::InvalidPacket)
|
514
|
-
end
|
536
|
+
it 'parses the response as a SMB1 WriteResponse packet' do
|
537
|
+
expect(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).with(raw_response)
|
538
|
+
file.send_recv_write
|
515
539
|
end
|
516
540
|
|
517
541
|
it 'raises an InvalidPacket exception if the response is not valid' do
|
@@ -0,0 +1,332 @@
|
|
1
|
+
RSpec.describe RubySMB::SMB2::PreauthIntegrityCapabilities do
|
2
|
+
subject(:capability) { described_class.new }
|
3
|
+
|
4
|
+
it { is_expected.to respond_to :hash_algorithm_count }
|
5
|
+
it { is_expected.to respond_to :salt_length }
|
6
|
+
it { is_expected.to respond_to :hash_algorithms }
|
7
|
+
it { is_expected.to respond_to :salt }
|
8
|
+
|
9
|
+
it 'is little endian' do
|
10
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#hash_algorithm_count' do
|
14
|
+
it 'is a 16-bit unsigned integer' do
|
15
|
+
expect(capability.hash_algorithm_count).to be_a BinData::Uint16le
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'is set to the #hash_algorithms array size' do
|
19
|
+
array = [1, 2, 3]
|
20
|
+
capability.hash_algorithms = array
|
21
|
+
expect(capability.hash_algorithm_count).to eq(array.size)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#salt_length' do
|
26
|
+
it 'is a 16-bit unsigned integer' do
|
27
|
+
expect(capability.salt_length).to be_a BinData::Uint16le
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'is set to the #salt string size' do
|
31
|
+
salt = 'my_random_salt'
|
32
|
+
capability.salt = salt
|
33
|
+
expect(capability.salt_length).to eq(salt.size)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#hash_algorithms' do
|
38
|
+
it 'is a BinData Array' do
|
39
|
+
expect(capability.hash_algorithms).to be_a BinData::Array
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'has #hash_algorithm_count elements' do
|
43
|
+
capability.hash_algorithm_count = 3
|
44
|
+
expect(capability.hash_algorithms.size).to eq 3
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#salt' do
|
49
|
+
it 'is a string' do
|
50
|
+
expect(capability.salt).to be_a BinData::String
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should read #salt_length bytes' do
|
54
|
+
salt = 'my_random_salt'
|
55
|
+
capability.salt_length = 5
|
56
|
+
expect(capability.salt.read(salt)).to eq(salt[0,5])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'reads binary data as expected' do
|
61
|
+
data = described_class.new(
|
62
|
+
hash_algorithms: [described_class::SHA_512],
|
63
|
+
salt: 'test salt'
|
64
|
+
)
|
65
|
+
expect(described_class.read(data.to_binary_s)).to eq(data)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
RSpec.describe RubySMB::SMB2::EncryptionCapabilities do
|
70
|
+
subject(:capability) { described_class.new }
|
71
|
+
|
72
|
+
it { is_expected.to respond_to :cipher_count }
|
73
|
+
it { is_expected.to respond_to :ciphers }
|
74
|
+
|
75
|
+
it 'is little endian' do
|
76
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#cipher_count' do
|
80
|
+
it 'is a 16-bit unsigned integer' do
|
81
|
+
expect(capability.cipher_count).to be_a BinData::Uint16le
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'is set to the #ciphers array size' do
|
85
|
+
array = [1, 2, 3]
|
86
|
+
capability.ciphers = array
|
87
|
+
expect(capability.cipher_count).to eq(array.size)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#ciphers' do
|
92
|
+
it 'is a BinData Array' do
|
93
|
+
expect(capability.ciphers).to be_a BinData::Array
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'has #cipher_count elements' do
|
97
|
+
capability.cipher_count = 3
|
98
|
+
expect(capability.ciphers.size).to eq 3
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'reads binary data as expected' do
|
103
|
+
data = described_class.new(
|
104
|
+
ciphers: [described_class::AES_128_CCM, described_class::AES_128_GCM]
|
105
|
+
)
|
106
|
+
expect(described_class.read(data.to_binary_s)).to eq(data)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
RSpec.describe RubySMB::SMB2::CompressionCapabilities do
|
111
|
+
subject(:capability) { described_class.new }
|
112
|
+
|
113
|
+
it { is_expected.to respond_to :compression_algorithm_count }
|
114
|
+
it { is_expected.to respond_to :padding }
|
115
|
+
it { is_expected.to respond_to :flags }
|
116
|
+
it { is_expected.to respond_to :compression_algorithms }
|
117
|
+
|
118
|
+
it 'is little endian' do
|
119
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#compression_algorithm_count' do
|
123
|
+
it 'is a 16-bit unsigned integer' do
|
124
|
+
expect(capability.compression_algorithm_count).to be_a BinData::Uint16le
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'is set to the #compression_algorithms array size' do
|
128
|
+
array = [1, 2, 3]
|
129
|
+
capability.compression_algorithms = array
|
130
|
+
expect(capability.compression_algorithm_count).to eq(array.size)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '#padding' do
|
135
|
+
it 'is a 16-bit unsigned integer' do
|
136
|
+
expect(capability.padding).to be_a BinData::Uint16le
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#flags' do
|
141
|
+
it 'is a 32-bit unsigned integer' do
|
142
|
+
expect(capability.flags).to be_a BinData::Uint32le
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#compression_algorithms' do
|
147
|
+
it 'is a BinData Array' do
|
148
|
+
expect(capability.compression_algorithms).to be_a BinData::Array
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'has #compression_algorithm_count elements' do
|
152
|
+
capability.compression_algorithm_count = 3
|
153
|
+
expect(capability.compression_algorithms.size).to eq 3
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'reads binary data as expected' do
|
158
|
+
data = described_class.new(
|
159
|
+
compression_algorithms: [described_class::LZNT1, described_class::LZ77]
|
160
|
+
)
|
161
|
+
expect(described_class.read(data.to_binary_s)).to eq(data)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
RSpec.describe RubySMB::SMB2::NetnameNegotiateContextId do
|
166
|
+
subject(:capability) { described_class.new }
|
167
|
+
|
168
|
+
it { is_expected.to respond_to :net_name }
|
169
|
+
|
170
|
+
it 'is little endian' do
|
171
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
172
|
+
end
|
173
|
+
|
174
|
+
describe '#net_name' do
|
175
|
+
it 'is a unicode string' do
|
176
|
+
expect(capability.net_name).to be_a RubySMB::Field::Stringz16
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'reads binary data as expected' do
|
181
|
+
data = described_class.new(
|
182
|
+
net_name: 'netname test'
|
183
|
+
)
|
184
|
+
expect(described_class.read(data.to_binary_s)).to eq(data)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
RSpec.describe RubySMB::SMB2::NegotiateContext do
|
189
|
+
class FakePacket < BinData::Record
|
190
|
+
endian :little
|
191
|
+
string :garbage
|
192
|
+
negotiate_context :nc
|
193
|
+
end
|
194
|
+
|
195
|
+
let(:test_packet) do
|
196
|
+
packet = FakePacket.new
|
197
|
+
packet.nc.context_type = described_class::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
198
|
+
packet
|
199
|
+
end
|
200
|
+
subject(:negotiate_context) { described_class.new }
|
201
|
+
|
202
|
+
it { is_expected.to respond_to :pad }
|
203
|
+
it { is_expected.to respond_to :context_type }
|
204
|
+
it { is_expected.to respond_to :data_length }
|
205
|
+
it { is_expected.to respond_to :reserved }
|
206
|
+
it { is_expected.to respond_to :data }
|
207
|
+
|
208
|
+
it 'is little endian' do
|
209
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
210
|
+
end
|
211
|
+
|
212
|
+
describe '#pad' do
|
213
|
+
it 'is a string' do
|
214
|
+
expect(negotiate_context.pad).to be_a BinData::String
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'should keep the #context_type 8-byte aligned' do
|
218
|
+
test_packet.garbage = 'foo'
|
219
|
+
expect(test_packet.nc.context_type.abs_offset % 8).to eq(0)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe '#context_type ' do
|
224
|
+
it 'is a 16-bit unsigned integer' do
|
225
|
+
expect(negotiate_context.context_type).to be_a BinData::Uint16le
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe '#data_length' do
|
230
|
+
it 'is a 16-bit unsigned integer' do
|
231
|
+
expect(negotiate_context.data_length).to be_a BinData::Uint16le
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'should give the #data field length in bytes' do
|
235
|
+
expect(described_class.new(context_type: described_class::SMB2_ENCRYPTION_CAPABILITIES).data_length)
|
236
|
+
.to eq(RubySMB::SMB2::EncryptionCapabilities.new.num_bytes)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe '#data' do
|
241
|
+
it 'is a BinData choice field' do
|
242
|
+
expect(negotiate_context.data).to be_a BinData::Choice
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'with a SMB2_PREAUTH_INTEGRITY_CAPABILITIES context type' do
|
246
|
+
it 'selects the PreauthIntegrityCapabilities structure' do
|
247
|
+
expect(described_class.new(context_type: described_class::SMB2_PREAUTH_INTEGRITY_CAPABILITIES).data)
|
248
|
+
.to eq(RubySMB::SMB2::PreauthIntegrityCapabilities.new)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context 'with a SMB2_ENCRYPTION_CAPABILITIES context type' do
|
253
|
+
it 'selects the PreauthIntegrityCapabilities structure' do
|
254
|
+
expect(described_class.new(context_type: described_class::SMB2_ENCRYPTION_CAPABILITIES).data)
|
255
|
+
.to eq(RubySMB::SMB2::EncryptionCapabilities.new)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context 'with a SMB2_COMPRESSION_CAPABILITIES context type' do
|
260
|
+
it 'selects the PreauthIntegrityCapabilities structure' do
|
261
|
+
expect(described_class.new(context_type: described_class::SMB2_COMPRESSION_CAPABILITIES).data)
|
262
|
+
.to eq(RubySMB::SMB2::CompressionCapabilities.new)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context 'with a SMB2_NETNAME_NEGOTIATE_CONTEXT_ID context type' do
|
267
|
+
it 'selects the PreauthIntegrityCapabilities structure' do
|
268
|
+
expect(described_class.new(context_type: described_class::SMB2_NETNAME_NEGOTIATE_CONTEXT_ID).data)
|
269
|
+
.to eq(RubySMB::SMB2::NetnameNegotiateContextId.new)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe '#pad_length' do
|
275
|
+
it 'returns 0 when #context_type is already 8-byte aligned' do
|
276
|
+
expect(test_packet.nc.pad_length).to eq(0)
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'returns 2 when #context_type is only 2-byte aligned' do
|
280
|
+
test_packet.garbage = 'align' + 'A'
|
281
|
+
expect(test_packet.nc.pad_length).to eq(2)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'with a SMB2_PREAUTH_INTEGRITY_CAPABILITIES context type' do
|
286
|
+
it 'reads binary data as expected' do
|
287
|
+
data = described_class.new(
|
288
|
+
context_type: described_class::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
289
|
+
)
|
290
|
+
data.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
291
|
+
data.data.salt = 'test salt'
|
292
|
+
expect(described_class.read(data.to_binary_s)).to eq(data)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
context 'with a SMB2_ENCRYPTION_CAPABILITIES context type' do
|
297
|
+
it 'reads binary data as expected' do
|
298
|
+
data = described_class.new(
|
299
|
+
context_type: described_class::SMB2_ENCRYPTION_CAPABILITIES
|
300
|
+
)
|
301
|
+
data.data.ciphers = [
|
302
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
|
303
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
304
|
+
]
|
305
|
+
expect(described_class.read(data.to_binary_s)).to eq(data)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
context 'with a SMB2_COMPRESSION_CAPABILITIES context type' do
|
310
|
+
it 'reads binary data as expected' do
|
311
|
+
data = described_class.new(
|
312
|
+
context_type: described_class::SMB2_COMPRESSION_CAPABILITIES
|
313
|
+
)
|
314
|
+
data.data.compression_algorithms = [
|
315
|
+
RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
316
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77
|
317
|
+
]
|
318
|
+
expect(described_class.read(data.to_binary_s)).to eq(data)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context 'with a SMB2_NETNAME_NEGOTIATE_CONTEXT_ID context type' do
|
323
|
+
it 'reads binary data as expected' do
|
324
|
+
data = described_class.new(
|
325
|
+
context_type: described_class::SMB2_NETNAME_NEGOTIATE_CONTEXT_ID
|
326
|
+
)
|
327
|
+
data.data.net_name = 'netname test'
|
328
|
+
expect(described_class.read(data.to_binary_s)).to eq(data)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|