ruby_smb 1.0.4 → 2.0.2
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 +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
|
+
|