ruby_smb 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -4
  4. data/.travis.yml +3 -5
  5. data/Gemfile +6 -2
  6. data/examples/negotiate.rb +51 -8
  7. data/examples/read_file_encryption.rb +56 -0
  8. data/lib/ruby_smb.rb +4 -0
  9. data/lib/ruby_smb/client.rb +172 -16
  10. data/lib/ruby_smb/client/authentication.rb +27 -8
  11. data/lib/ruby_smb/client/encryption.rb +62 -0
  12. data/lib/ruby_smb/client/negotiation.rb +133 -12
  13. data/lib/ruby_smb/client/signing.rb +19 -0
  14. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  15. data/lib/ruby_smb/client/utils.rb +8 -7
  16. data/lib/ruby_smb/crypto.rb +30 -0
  17. data/lib/ruby_smb/dispatcher/socket.rb +2 -2
  18. data/lib/ruby_smb/error.rb +28 -1
  19. data/lib/ruby_smb/smb1/commands.rb +1 -1
  20. data/lib/ruby_smb/smb1/file.rb +4 -4
  21. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  22. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  23. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  24. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  25. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  26. data/lib/ruby_smb/smb1/pipe.rb +2 -2
  27. data/lib/ruby_smb/smb1/tree.rb +3 -3
  28. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  29. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  30. data/lib/ruby_smb/smb2/file.rb +25 -43
  31. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  32. data/lib/ruby_smb/smb2/packet.rb +2 -0
  33. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  34. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  35. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +49 -3
  36. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  37. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  38. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  39. data/lib/ruby_smb/smb2/pipe.rb +3 -16
  40. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  41. data/lib/ruby_smb/smb2/tree.rb +23 -17
  42. data/lib/ruby_smb/version.rb +1 -1
  43. data/ruby_smb.gemspec +3 -1
  44. data/spec/lib/ruby_smb/client_spec.rb +1256 -57
  45. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  46. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  47. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  48. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  49. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  50. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  51. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  52. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  53. data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
  54. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  55. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  56. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  57. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  58. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  59. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  60. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  61. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +0 -40
  62. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  63. data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
  64. metadata +124 -75
  65. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3fe1b1b7bdd2516bd388f86c020a9fa99b70faa0
4
- data.tar.gz: aa11ed030f543c0d83b61929d10c35eea20a8455
2
+ SHA256:
3
+ metadata.gz: c76e06c7e09d56565d9253523f519ac12eccc193201b8f4dcd99a8ed07b37991
4
+ data.tar.gz: b533650ae3c9c16e75f2c5b987f53055894f0a99e87151dc5838922882d52ade
5
5
  SHA512:
6
- metadata.gz: 92cc9708588a610104f8bacc8430fedd537f2ba283bb323b791ff45005a43417cf9570ce63f658b7a38c566570c2b651adb17b6750b3b117279552efaf138cab
7
- data.tar.gz: 071db1651283d080b2adc728cd1f2225e051d52f86608f9f54dd6d79d39330127c3b5179d143ec9e5e155d132af1e128c4c9ca42744264caec8b249e0b0c86ec
6
+ metadata.gz: 82cbd9940e99aea529d23046e26b05f92bcfff3e03ee8df852b0c44f7b50ef517e4549f3e46b6890f55f0c5fc34e624821701cea6b807c008d1cbb03f0e4f242
7
+ data.tar.gz: 7d91fa41048597a8c376af79b32374588ffdb79b114bacfb1b43e482f22298f6439fe92a13753abffeb7b329ba3f09f4131a23bf8e63d62c5f9f85da40c5f5e0
Binary file
data.tar.gz.sig CHANGED
@@ -1,4 +1 @@
1
- VQ��kDfڸZ
2
- =���U<�5/�Am�샜(��Ύm���pe�%u}�TQGh�0,�y��/��E#�M���W(A�˛\Y�M��doF�
3
- �6�T����U��6��1F�ex@��pg�r�F]W�2WM?�%�z�c# 2��*U.��*
4
- }�$��h@�uN�q�'�����c�
1
+ Zp����xD�&Ǻ�(�T�q�Q�6Q��h_࠘4m��ң��1�Q��S�"B'��M���=�g_|i�ЈJz�����������MN-��s��O��&窓�Ӿ�~��*E�l�a�A<>��F�%�_�?�����n�1R�~��z
@@ -1,8 +1,6 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
- - '2.3.8'
5
- - '2.4.5'
6
- - '2.5.3'
7
- - '2.6.1'
8
- - 'jruby-9000'
4
+ - '2.5.8'
5
+ - '2.6.6'
6
+ - '2.7.0'
data/Gemfile CHANGED
@@ -1,11 +1,15 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
- gem 'pry'
4
+ group :development do
5
+ # for development and testing purposes
6
+ gem 'pry-byebug'
7
+ gem 'pry-rescue'
8
+ end
5
9
 
6
10
  group :test do
7
11
  # simplecov test formatter and uploader for Coveralls.io
8
- gem 'coveralls', require: false
12
+ gem "coveralls", '~>0.8.23', :require => false
9
13
  # Testing
10
14
  gem 'rspec'
11
15
  # Coverage reports
@@ -7,18 +7,61 @@
7
7
  require 'bundler/setup'
8
8
  require 'ruby_smb'
9
9
 
10
- def run_negotiation(address, smb1, smb2)
10
+ def run_negotiation(address, smb1, smb2, smb3, opts = {})
11
11
  # Create our socket and add it to the dispatcher
12
12
  sock = TCPSocket.new address, 445
13
13
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
14
14
 
15
- client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, username: 'msfadmin', password: 'msfadmin')
15
+ client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: 'msfadmin', password: 'msfadmin')
16
16
  client.negotiate
17
17
  end
18
18
 
19
- # Negotiate with both SMB1 and SMB2 enabled on the client
20
- run_negotiation(ARGV[0], true, true)
21
- # Negotiate with only SMB1 enabled
22
- run_negotiation(ARGV[0], true, false)
23
- # Negotiate with only SMB2 enabled
24
- run_negotiation(ARGV[0], false, true)
19
+ begin
20
+ puts "Negotiate with only SMB1 enabled..."
21
+ puts " Negotiated version: #{run_negotiation(ARGV[0], true, false, false)}"
22
+ rescue RubySMB::Error::RubySMBError => e
23
+ puts "Error: #{e.message}"
24
+ end
25
+
26
+ begin
27
+ puts "Negotiate with only SMB2 enabled..."
28
+ puts " Negotiated version: #{run_negotiation(ARGV[0], false, true, false)}"
29
+ rescue RubySMB::Error::RubySMBError => e
30
+ puts "Error: #{e.message}"
31
+ end
32
+
33
+ begin
34
+ puts "Negotiate with only SMB3 enabled..."
35
+ puts " Negotiated version: #{run_negotiation(ARGV[0], false, false, true)}"
36
+ rescue RubySMB::Error::RubySMBError => e
37
+ puts "Error: #{e.message}"
38
+ end
39
+
40
+ begin
41
+ puts "Negotiate with both SMB1 and SMB2 enabled on the client..."
42
+ puts " Negotiated version: #{run_negotiation(ARGV[0], true, true, false)}"
43
+ rescue RubySMB::Error::RubySMBError => e
44
+ puts "Error: #{e.message}"
45
+ end
46
+
47
+ begin
48
+ puts "Negotiate with both SMB2 and SMB3 enabled on the client..."
49
+ puts " Negotiated version: #{run_negotiation(ARGV[0], false, true, true)}"
50
+ rescue RubySMB::Error::RubySMBError => e
51
+ puts "Error: #{e.message}"
52
+ end
53
+
54
+ begin
55
+ puts "Negotiate with both SMB1 and SMB3 enabled on the client..."
56
+ puts " Negotiated version: #{run_negotiation(ARGV[0], true, false, true)}"
57
+ rescue RubySMB::Error::RubySMBError => e
58
+ puts "Error: #{e.message}"
59
+ end
60
+
61
+ begin
62
+ puts "Negotiate with SMB1, SMB2 and SMB3 enabled on the client..."
63
+ puts " Negotiated version: #{run_negotiation(ARGV[0], true, true, true)}"
64
+ rescue RubySMB::Error::RubySMBError => e
65
+ puts "Error: #{e.message}"
66
+ end
67
+
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # This example script is used for testing the reading of a file.
4
+ # It will attempt to connect to a specific share and then read a specified file.
5
+ # Example usage: ruby read_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE short.txt
6
+ # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials
7
+ # and read the file short.txt
8
+
9
+ require 'bundler/setup'
10
+ require 'ruby_smb'
11
+
12
+ address = ARGV[0]
13
+ username = ARGV[1]
14
+ password = ARGV[2]
15
+ share = ARGV[3]
16
+ filename = ARGV[4]
17
+ path = "\\\\#{address}\\#{share}"
18
+
19
+ sock = TCPSocket.new address, 445
20
+ dispatcher = RubySMB::Dispatcher::Socket.new(sock)
21
+
22
+ # To require encryption on the server, run this in an elevated Powershell:
23
+ # C:\> Set-SmbServerConfiguration -EncryptData $true
24
+
25
+ # To enable per-share encryption on the server, run this in an elevated Powershell:
26
+ # C:\ Set-SmbServerConfiguration -EncryptData $false
27
+ # C:\ Set-SmbShare -Name <share name> -EncryptData 1
28
+
29
+ # For this encryption to work, it has to be SMBv3. By only setting smb3 to true,
30
+ # we make sure the server will negotiate this version, if it supports it
31
+ opts = {
32
+ smb1: false,
33
+ smb2: false,
34
+ smb3: true,
35
+ username: username,
36
+ password: password,
37
+ }
38
+
39
+ # By default, the client uses encryption even if it is not required by the server. Disable this by setting always_encrypt to false
40
+ #opts[:always_encrypt] = false
41
+
42
+ client = RubySMB::Client.new(dispatcher, opts)
43
+ protocol = client.negotiate
44
+ status = client.authenticate
45
+
46
+ begin
47
+ tree = client.tree_connect(path)
48
+ rescue StandardError => e
49
+ puts "Failed to connect to #{path}: #{e.message}"
50
+ end
51
+
52
+ file = tree.open_file(filename: filename)
53
+
54
+ data = file.read
55
+ puts data
56
+ file.close
@@ -1,6 +1,9 @@
1
1
  require 'bindata'
2
2
  require 'net/ntlm'
3
3
  require 'net/ntlm/client'
4
+ require 'openssl'
5
+ require 'openssl/ccm'
6
+ require 'openssl/cmac'
4
7
  require 'windows_error'
5
8
  require 'windows_error/nt_status'
6
9
  # A packet parsing and manipulation library for the SMB1 and SMB2 protocols
@@ -22,4 +25,5 @@ module RubySMB
22
25
  require 'ruby_smb/smb2'
23
26
  require 'ruby_smb/smb1'
24
27
  require 'ruby_smb/client'
28
+ require 'ruby_smb/crypto'
25
29
  end
@@ -9,6 +9,7 @@ module RubySMB
9
9
  require 'ruby_smb/client/echo'
10
10
  require 'ruby_smb/client/utils'
11
11
  require 'ruby_smb/client/winreg'
12
+ require 'ruby_smb/client/encryption'
12
13
 
13
14
  include RubySMB::Client::Negotiation
14
15
  include RubySMB::Client::Authentication
@@ -17,13 +18,20 @@ module RubySMB
17
18
  include RubySMB::Client::Echo
18
19
  include RubySMB::Client::Utils
19
20
  include RubySMB::Client::Winreg
21
+ include RubySMB::Client::Encryption
20
22
 
21
23
  # The Default SMB1 Dialect string used in an SMB1 Negotiate Request
22
24
  SMB1_DIALECT_SMB1_DEFAULT = 'NT LM 0.12'.freeze
23
25
  # The Default SMB2 Dialect string used in an SMB1 Negotiate Request
24
26
  SMB1_DIALECT_SMB2_DEFAULT = 'SMB 2.002'.freeze
25
- # Dialect value for SMB2 Default (Version 2.02)
26
- SMB2_DIALECT_DEFAULT = 0x0202
27
+ # The SMB2 wildcard revision number Dialect string used in an SMB1 Negotiate Request
28
+ # It indicates that the server implements SMB 2.1 or future dialect revisions
29
+ # Note that this must be used for SMB3
30
+ SMB1_DIALECT_SMB2_WILDCARD = 'SMB 2.???'.freeze
31
+ # Dialect values for SMB2
32
+ SMB2_DIALECT_DEFAULT = ['0x0202', '0x0210']
33
+ # Dialect values for SMB3
34
+ SMB3_DIALECT_DEFAULT = ['0x0300', '0x0302', '0x0311']
27
35
  # The default maximum size of a SMB message that the Client accepts (in bytes)
28
36
  MAX_BUFFER_SIZE = 64512
29
37
  # The default maximum size of a SMB message that the Server accepts (in bytes)
@@ -135,6 +143,11 @@ module RubySMB
135
143
  # @return [Boolean]
136
144
  attr_accessor :smb2
137
145
 
146
+ # Whether or not the Client should support SMB3
147
+ # @!attribute [rw] smb3
148
+ # @return [Boolean]
149
+ attr_accessor :smb3
150
+
138
151
  # Tracks the current SMB2 Message ID that keeps communication in sync
139
152
  # @!attribute [rw] smb2_message_id
140
153
  # @return [Integer]
@@ -178,12 +191,61 @@ module RubySMB
178
191
  # @return [Integer]
179
192
  attr_accessor :server_max_transact_size
180
193
 
194
+ # The algorithm to compute the preauthentication integrity hash (SMB 3.1.1).
195
+ # @!attribute [rw] preauth_integrity_hash_algorithm
196
+ # @return [String]
197
+ attr_accessor :preauth_integrity_hash_algorithm
198
+
199
+ # The preauthentication integrity hash value (SMB 3.1.1).
200
+ # @!attribute [rw] preauth_integrity_hash_value
201
+ # @return [String]
202
+ attr_accessor :preauth_integrity_hash_value
203
+
204
+ # The algorithm for encryption (SMB 3.x).
205
+ # @!attribute [rw] encryption_algorithm
206
+ # @return [String]
207
+ attr_accessor :encryption_algorithm
208
+
209
+ # The client encryption key (SMB 3.x).
210
+ # @!attribute [rw] client_encryption_key
211
+ # @return [String]
212
+ attr_accessor :client_encryption_key
213
+
214
+ # The server encryption key (SMB 3.x).
215
+ # @!attribute [rw] server_encryption_key
216
+ # @return [String]
217
+ attr_accessor :server_encryption_key
218
+
219
+ # Whether or not encryption is required (SMB 3.x)
220
+ # @!attribute [rw] encryption_required
221
+ # @return [Boolean]
222
+ attr_accessor :encryption_required
223
+
224
+ # The encryption algorithms supported by the server (SMB 3.x).
225
+ # @!attribute [rw] server_encryption_algorithms
226
+ # @return [Array<Integer>] list of supported encryption algorithms
227
+ # (constants defined in RubySMB::SMB2::EncryptionCapabilities)
228
+ attr_accessor :server_encryption_algorithms
229
+
230
+ # The compression algorithms supported by the server (SMB 3.x).
231
+ # @!attribute [rw] server_compression_algorithms
232
+ # @return [Array<Integer>] list of supported compression algorithms
233
+ # (constants defined in RubySMB::SMB2::CompressionCapabilities)
234
+ attr_accessor :server_compression_algorithms
235
+
236
+ # The SMB version that has been successfully negotiated. This value is only
237
+ # set after the NEGOTIATE handshake has been performed.
238
+ # @!attribute [rw] negotiated_smb_version
239
+ # @return [Integer] the negotiated SMB version
240
+ attr_accessor :negotiated_smb_version
241
+
181
242
  # @param dispatcher [RubySMB::Dispatcher::Socket] the packet dispatcher to use
182
243
  # @param smb1 [Boolean] whether or not to enable SMB1 support
183
244
  # @param smb2 [Boolean] whether or not to enable SMB2 support
184
- def initialize(dispatcher, smb1: true, smb2: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION')
245
+ # @param smb3 [Boolean] whether or not to enable SMB3 support
246
+ def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION', always_encrypt: true)
185
247
  raise ArgumentError, 'No Dispatcher provided' unless dispatcher.is_a? RubySMB::Dispatcher::Base
186
- if smb1 == false && smb2 == false
248
+ if smb1 == false && smb2 == false && smb3 == false
187
249
  raise ArgumentError, 'You must enable at least one Protocol'
188
250
  end
189
251
  @dispatcher = dispatcher
@@ -196,6 +258,7 @@ module RubySMB
196
258
  @signing_required = false
197
259
  @smb1 = smb1
198
260
  @smb2 = smb2
261
+ @smb3 = smb3
199
262
  @username = username.encode('utf-8') || ''.encode('utf-8')
200
263
  @max_buffer_size = MAX_BUFFER_SIZE
201
264
  # These sizes will be modifed during negotiation
@@ -204,6 +267,9 @@ module RubySMB
204
267
  @server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
205
268
  @server_max_transact_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
206
269
 
270
+ # SMB 3.x options
271
+ @encryption_required = always_encrypt
272
+
207
273
  negotiate_version_flag = 0x02000000
208
274
  flags = Net::NTLM::Client::DEFAULT_FLAGS |
209
275
  Net::NTLM::FLAGS[:TARGET_INFO] |
@@ -243,7 +309,7 @@ module RubySMB
243
309
  # @param data [String] the data the server should echo back (ignored in SMB2)
244
310
  # @return [WindowsError::ErrorCode] the NTStatus of the last response received
245
311
  def echo(count: 1, data: '')
246
- response = if smb2
312
+ response = if smb2 || smb3
247
313
  smb2_echo
248
314
  else
249
315
  smb1_echo(count: count, data: data)
@@ -258,10 +324,8 @@ module RubySMB
258
324
  # @param packet [RubySMB::GenericPacket] the packet to set the message id for
259
325
  # @return [RubySMB::GenericPacket] the modified packet
260
326
  def increment_smb_message_id(packet)
261
- if packet.smb2_header.message_id.zero? && smb2_message_id != 0
262
- packet.smb2_header.message_id = smb2_message_id
263
- self.smb2_message_id += 1
264
- end
327
+ packet.smb2_header.message_id = smb2_message_id
328
+ self.smb2_message_id += 1
265
329
  packet
266
330
  end
267
331
 
@@ -301,7 +365,7 @@ module RubySMB
301
365
  # @return [WindowsError::ErrorCode] the NTStatus of the response
302
366
  # @raise [RubySMB::Error::InvalidPacket] if the response packet is not a LogoffResponse packet
303
367
  def logoff!
304
- if smb2
368
+ if smb2 || smb3
305
369
  request = RubySMB::SMB2::Packet::LogoffRequest.new
306
370
  raw_response = send_recv(request)
307
371
  response = RubySMB::SMB2::Packet::LogoffResponse.read(raw_response)
@@ -335,8 +399,9 @@ module RubySMB
335
399
  #
336
400
  # @param packet [RubySMB::GenericPacket] the request to be sent
337
401
  # @return [String] the raw response data received
338
- def send_recv(packet)
339
- case packet.packet_smb_version
402
+ def send_recv(packet, encrypt: false)
403
+ version = packet.packet_smb_version
404
+ case version
340
405
  when 'SMB1'
341
406
  packet.smb_header.uid = user_id if user_id
342
407
  packet = smb1_sign(packet)
@@ -344,25 +409,103 @@ module RubySMB
344
409
  packet = increment_smb_message_id(packet)
345
410
  packet.smb2_header.session_id = session_id
346
411
  unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
347
- packet = smb2_sign(packet)
412
+ if self.smb2
413
+ packet = smb2_sign(packet)
414
+ elsif self.smb3
415
+ packet = smb3_sign(packet)
416
+ end
348
417
  end
349
418
  else
350
419
  packet = packet
351
420
  end
352
- dispatcher.send_packet(packet)
353
- raw_response = dispatcher.recv_packet
421
+
422
+ if can_be_encrypted?(packet) && encryption_supported? && (@encryption_required || encrypt)
423
+ send_encrypt(packet)
424
+ raw_response = recv_encrypt
425
+ loop do
426
+ break unless is_status_pending?(raw_response)
427
+ sleep 1
428
+ raw_response = recv_encrypt
429
+ end
430
+ else
431
+ dispatcher.send_packet(packet)
432
+ raw_response = dispatcher.recv_packet
433
+ loop do
434
+ break unless is_status_pending?(raw_response)
435
+ sleep 1
436
+ raw_response = dispatcher.recv_packet
437
+ end unless version == 'SMB1'
438
+ end
354
439
 
355
440
  self.sequence_counter += 1 if signing_required && !session_key.empty?
356
441
  raw_response
357
442
  end
358
443
 
444
+ # Check if the response is an asynchronous operation with STATUS_PENDING
445
+ # status code.
446
+ #
447
+ # @param raw_response [String] the raw response packet
448
+ # @return [Boolean] true if it is a status pending operation, false otherwise
449
+ def is_status_pending?(raw_response)
450
+ smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
451
+ value = smb2_header.nt_status.value
452
+ status_code = WindowsError::NTStatus.find_by_retval(value).first
453
+ status_code == WindowsError::NTStatus::STATUS_PENDING &&
454
+ smb2_header.flags.async_command == 1
455
+ end
456
+
457
+ # Check if the request packet can be encrypted. Per the SMB spec,
458
+ # SessionSetupRequest and NegotiateRequest must not be encrypted.
459
+ #
460
+ # @param packet [RubySMB::GenericPacket] the request packet
461
+ # @return [Boolean] true if the packet can be encrypted
462
+ def can_be_encrypted?(packet)
463
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].none? do |klass|
464
+ packet.is_a?(klass)
465
+ end
466
+ end
467
+
468
+ # Check if the current dialect support encryption.
469
+ #
470
+ # @return [Boolean] true if encryption is supported
471
+ def encryption_supported?
472
+ ['0x0300', '0x0302', '0x0311'].include?(@dialect)
473
+ end
474
+
475
+ # Encrypt and send a packet
476
+ def send_encrypt(packet)
477
+ begin
478
+ transform_request = smb3_encrypt(packet.to_binary_s)
479
+ rescue RubySMB::Error::RubySMBError => e
480
+ raise RubySMB::Error::EncryptionError, "Error while encrypting #{packet.class.name} packet (SMB #{@dialect}): #{e}"
481
+ end
482
+ dispatcher.send_packet(transform_request)
483
+ end
484
+
485
+ # Receives the raw response through the Dispatcher and decrypt the packet.
486
+ #
487
+ # @return [String] the raw unencrypted packet
488
+ def recv_encrypt
489
+ raw_response = dispatcher.recv_packet
490
+ begin
491
+ transform_response = RubySMB::SMB2::Packet::TransformHeader.read(raw_response)
492
+ rescue IOError
493
+ raise RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet'
494
+ end
495
+ begin
496
+ smb3_decrypt(transform_response)
497
+ rescue RubySMB::Error::RubySMBError => e
498
+ raise RubySMB::Error::EncryptionError, "Error while decrypting #{transform_response.class.name} packet (SMB #@dialect}): #{e}"
499
+ end
500
+ end
501
+
359
502
  # Connects to the supplied share
360
503
  #
361
504
  # @param share [String] the path to the share in `\\server\share_name` format
362
505
  # @return [RubySMB::SMB1::Tree] if talking over SMB1
363
506
  # @return [RubySMB::SMB2::Tree] if talking over SMB2
364
507
  def tree_connect(share)
365
- connected_tree = if smb2
508
+ connected_tree = if smb2 || smb3
366
509
  smb2_tree_connect(share)
367
510
  else
368
511
  smb1_tree_connect(share)
@@ -392,6 +535,8 @@ module RubySMB
392
535
  self.session_key = ''
393
536
  self.sequence_counter = 0
394
537
  self.smb2_message_id = 0
538
+ self.client_encryption_key = nil
539
+ self.server_encryption_key = nil
395
540
  end
396
541
 
397
542
  # Requests a NetBIOS Session Service using the provided name.
@@ -434,5 +579,16 @@ module RubySMB
434
579
  session_request
435
580
  end
436
581
 
582
+ def update_preauth_hash(data)
583
+ unless @preauth_integrity_hash_algorithm
584
+ raise RubySMB::Error::EncryptionError.new(
585
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
586
+ )
587
+ end
588
+ @preauth_integrity_hash_value = OpenSSL::Digest.digest(
589
+ @preauth_integrity_hash_algorithm,
590
+ @preauth_integrity_hash_value + data.to_binary_s
591
+ )
592
+ end
437
593
  end
438
594
  end