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.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/enum_registry_key.rb +28 -0
  8. data/examples/enum_registry_values.rb +30 -0
  9. data/examples/negotiate.rb +51 -8
  10. data/examples/pipes.rb +2 -1
  11. data/examples/read_file_encryption.rb +56 -0
  12. data/examples/read_registry_key_value.rb +32 -0
  13. data/lib/ruby_smb.rb +4 -1
  14. data/lib/ruby_smb/client.rb +207 -18
  15. data/lib/ruby_smb/client/authentication.rb +27 -8
  16. data/lib/ruby_smb/client/encryption.rb +62 -0
  17. data/lib/ruby_smb/client/negotiation.rb +153 -12
  18. data/lib/ruby_smb/client/signing.rb +19 -0
  19. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  20. data/lib/ruby_smb/client/utils.rb +8 -7
  21. data/lib/ruby_smb/client/winreg.rb +46 -0
  22. data/lib/ruby_smb/crypto.rb +30 -0
  23. data/lib/ruby_smb/dcerpc.rb +38 -0
  24. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  25. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  27. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  28. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  29. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  30. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  31. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  33. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  34. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  36. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  40. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  44. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  48. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  49. data/lib/ruby_smb/dispatcher/socket.rb +4 -3
  50. data/lib/ruby_smb/error.rb +28 -1
  51. data/lib/ruby_smb/smb1/commands.rb +1 -1
  52. data/lib/ruby_smb/smb1/file.rb +6 -4
  53. data/lib/ruby_smb/smb1/packet/empty_packet.rb +4 -2
  54. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  55. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  56. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  57. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  58. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  59. data/lib/ruby_smb/smb1/pipe.rb +79 -3
  60. data/lib/ruby_smb/smb1/tree.rb +12 -3
  61. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  62. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  63. data/lib/ruby_smb/smb2/file.rb +25 -43
  64. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  65. data/lib/ruby_smb/smb2/packet.rb +2 -0
  66. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  67. data/lib/ruby_smb/smb2/packet/error_packet.rb +9 -4
  68. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  69. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  70. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  71. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  72. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  73. data/lib/ruby_smb/smb2/pipe.rb +77 -3
  74. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  75. data/lib/ruby_smb/smb2/tree.rb +23 -17
  76. data/lib/ruby_smb/version.rb +1 -1
  77. data/ruby_smb.gemspec +5 -3
  78. data/spec/lib/ruby_smb/client_spec.rb +1441 -61
  79. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  80. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  81. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  82. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  83. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  84. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  85. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  86. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  87. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  88. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  89. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  90. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  91. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  92. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  93. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  94. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  95. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  96. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  97. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  98. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  99. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  100. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  101. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  102. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  103. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  104. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
  105. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  106. data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
  107. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +10 -0
  108. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  109. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  110. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  111. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  112. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +210 -148
  113. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  114. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  115. data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
  116. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  117. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  118. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +29 -2
  119. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  120. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  121. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  122. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  123. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  124. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +220 -149
  125. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  126. data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
  127. metadata +187 -81
  128. metadata.gz.sig +0 -0
  129. data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
  130. 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 'parses the response as a SMB1 WriteResponse packet' do
486
- expect(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).with(raw_response)
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
- context 'when the response status code is STATUS_PENDING' do
491
- before :example do
492
- allow(file).to receive(:sleep)
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
+