ruby_smb 1.1.0 → 2.0.4

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 (163) 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 -5
  5. data/Gemfile +6 -2
  6. data/examples/anonymous_auth.rb +3 -3
  7. data/examples/append_file.rb +10 -8
  8. data/examples/authenticate.rb +9 -5
  9. data/examples/delete_file.rb +8 -6
  10. data/examples/enum_registry_key.rb +5 -4
  11. data/examples/enum_registry_values.rb +5 -4
  12. data/examples/list_directory.rb +8 -6
  13. data/examples/negotiate.rb +51 -8
  14. data/examples/negotiate_with_netbios_service.rb +9 -5
  15. data/examples/net_share_enum_all.rb +6 -4
  16. data/examples/pipes.rb +11 -12
  17. data/examples/query_service_status.rb +64 -0
  18. data/examples/read_file.rb +8 -6
  19. data/examples/read_file_encryption.rb +56 -0
  20. data/examples/read_registry_key_value.rb +6 -5
  21. data/examples/rename_file.rb +9 -7
  22. data/examples/tree_connect.rb +7 -5
  23. data/examples/write_file.rb +9 -7
  24. data/lib/ruby_smb.rb +4 -0
  25. data/lib/ruby_smb/client.rb +246 -26
  26. data/lib/ruby_smb/client/authentication.rb +32 -18
  27. data/lib/ruby_smb/client/echo.rb +2 -4
  28. data/lib/ruby_smb/client/encryption.rb +62 -0
  29. data/lib/ruby_smb/client/negotiation.rb +156 -16
  30. data/lib/ruby_smb/client/signing.rb +19 -0
  31. data/lib/ruby_smb/client/tree_connect.rb +6 -8
  32. data/lib/ruby_smb/client/utils.rb +24 -17
  33. data/lib/ruby_smb/client/winreg.rb +1 -1
  34. data/lib/ruby_smb/crypto.rb +30 -0
  35. data/lib/ruby_smb/dcerpc.rb +2 -0
  36. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  37. data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
  38. data/lib/ruby_smb/dcerpc/request.rb +13 -0
  39. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
  40. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
  41. data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
  42. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
  43. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
  44. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
  45. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
  46. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
  47. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
  48. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
  49. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
  50. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
  51. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
  52. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
  53. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
  54. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
  55. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
  56. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
  57. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
  58. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
  59. data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
  60. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
  61. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
  62. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
  63. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
  64. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
  65. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
  66. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
  67. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
  68. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
  69. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
  70. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
  71. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  72. data/lib/ruby_smb/dispatcher/socket.rb +5 -4
  73. data/lib/ruby_smb/error.rb +49 -6
  74. data/lib/ruby_smb/field/stringz16.rb +17 -1
  75. data/lib/ruby_smb/generic_packet.rb +11 -1
  76. data/lib/ruby_smb/nbss/session_header.rb +4 -4
  77. data/lib/ruby_smb/smb1/commands.rb +1 -1
  78. data/lib/ruby_smb/smb1/file.rb +13 -28
  79. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  80. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  81. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  83. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  84. data/lib/ruby_smb/smb1/pipe.rb +8 -8
  85. data/lib/ruby_smb/smb1/tree.rb +25 -12
  86. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  87. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  88. data/lib/ruby_smb/smb2/file.rb +59 -77
  89. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  90. data/lib/ruby_smb/smb2/packet.rb +2 -0
  91. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  92. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  93. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  94. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  95. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  96. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  97. data/lib/ruby_smb/smb2/pipe.rb +8 -20
  98. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  99. data/lib/ruby_smb/smb2/tree.rb +44 -28
  100. data/lib/ruby_smb/version.rb +1 -1
  101. data/ruby_smb.gemspec +3 -1
  102. data/spec/lib/ruby_smb/client_spec.rb +1408 -70
  103. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  104. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
  105. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
  106. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
  107. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
  108. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
  109. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
  110. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
  111. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
  112. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
  113. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
  114. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
  115. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
  116. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
  117. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
  118. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
  119. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
  120. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
  121. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
  122. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
  123. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
  124. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
  125. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
  126. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
  127. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
  128. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
  129. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
  130. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
  131. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
  132. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
  133. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
  134. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
  135. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
  136. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +227 -41
  137. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +12 -12
  138. data/spec/lib/ruby_smb/error_spec.rb +88 -0
  139. data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
  140. data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
  141. data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
  142. data/spec/lib/ruby_smb/smb1/file_spec.rb +1 -3
  143. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  146. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  147. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +30 -5
  148. data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
  149. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  150. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  151. data/spec/lib/ruby_smb/smb2/file_spec.rb +147 -71
  152. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  153. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  154. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  155. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  156. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  157. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  158. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  159. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +9 -45
  160. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  161. data/spec/lib/ruby_smb/smb2/tree_spec.rb +111 -9
  162. metadata +194 -75
  163. metadata.gz.sig +2 -1
@@ -6,6 +6,14 @@ module RubySMB
6
6
  class TreeConnectResponse < RubySMB::GenericPacket
7
7
  COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
8
8
 
9
+ # Share Types
10
+ # Physical disk share
11
+ SMB2_SHARE_TYPE_DISK = 0x01
12
+ # Named pipe share
13
+ SMB2_SHARE_TYPE_PIPE = 0x02
14
+ # Printer share
15
+ SMB2_SHARE_TYPE_PRINT = 0x03
16
+
9
17
  endian :little
10
18
  smb2_header :smb2_header
11
19
  uint16 :structure_size, label: 'Structure Size', initial_value: 16
@@ -20,32 +28,6 @@ module RubySMB
20
28
  smb2_header.flags.reply = 1
21
29
  end
22
30
 
23
- # Returns the ACCESS_MASK for the Maximal Share Access Rights. The packet
24
- # defaults this to a {RubySMB::SMB2::BitField::DirectoryAccessMask}. If it is anything other than
25
- # a directory that has been connected to, it will re-cast it as a {RubySMB::SMB2::BitField::FileAccessMask}
26
- #
27
- # @return [RubySMB::SMB2::BitField::DirectoryAccessMask] if a directory was connected to
28
- # @return [RubySMB::SMB2::BitField::FileAccessMask] if anything else was connected to
29
- # @raise [RubySMB::Error::InvalidBitField] if ACCESS_MASK bit field is not valid
30
- def access_rights
31
- if is_directory?
32
- maximal_access
33
- else
34
- mask = maximal_access.to_binary_s
35
- begin
36
- RubySMB::SMB2::BitField::FileAccessMask.read(mask)
37
- rescue IOError
38
- raise RubySMB::Error::InvalidBitField, 'Invalid ACCESS_MASK for the Maximal Share Access Rights'
39
- end
40
- end
41
- end
42
-
43
- # Checks if the remote Tree is a directory
44
- #
45
- # @return [Boolean]
46
- def is_directory?
47
- share_type == 0x01
48
- end
49
31
  end
50
32
  end
51
33
  end
@@ -17,6 +17,8 @@ module RubySMB
17
17
  extend RubySMB::Dcerpc::Srvsvc
18
18
  when 'winreg'
19
19
  extend RubySMB::Dcerpc::Winreg
20
+ when 'svcctl'
21
+ extend RubySMB::Dcerpc::Svcctl
20
22
  end
21
23
  super(tree: tree, response: response, name: name)
22
24
  end
@@ -40,13 +42,12 @@ module RubySMB
40
42
  raise RubySMB::Error::InvalidPacket.new(
41
43
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
42
44
  expected_cmd: RubySMB::SMB2::Packet::IoctlResponse::COMMAND,
43
- received_proto: response.smb2_header.protocol,
44
- received_cmd: response.smb2_header.command
45
+ packet: response
45
46
  )
46
47
  end
47
48
 
48
49
  unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
49
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
50
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
50
51
  end
51
52
  response
52
53
  end
@@ -89,6 +90,7 @@ module RubySMB
89
90
  request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
90
91
  request.ctl_code = 0x0011C017
91
92
  request.flags.is_fsctl = 0x00000001
93
+ # TODO: handle fragmentation when the request size > MAX_XMIT_FRAG
92
94
  request.buffer = action.to_binary_s
93
95
 
94
96
  ioctl_raw_response = @tree.client.send_recv(request)
@@ -97,26 +99,12 @@ module RubySMB
97
99
  raise RubySMB::Error::InvalidPacket.new(
98
100
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
99
101
  expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
100
- received_proto: ioctl_response.smb2_header.protocol,
101
- received_cmd: ioctl_response.smb2_header.command
102
+ packet: ioctl_response
102
103
  )
103
104
  end
104
- # TODO: improve the handling of STATUS_PENDING responses
105
- if ioctl_response.status_code == WindowsError::NTStatus::STATUS_PENDING
106
- sleep 1
107
- ioctl_raw_response = @tree.client.dispatcher.recv_packet
108
- ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
109
- unless ioctl_response.valid?
110
- raise RubySMB::Error::InvalidPacket.new(
111
- expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
112
- expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
113
- received_proto: ioctl_response.smb2_header.protocol,
114
- received_cmd: ioctl_response.smb2_header.command
115
- )
116
- end
117
- elsif ![WindowsError::NTStatus::STATUS_SUCCESS,
105
+ unless [WindowsError::NTStatus::STATUS_SUCCESS,
118
106
  WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
119
- raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code.name
107
+ raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
120
108
  end
121
109
 
122
110
  raw_data = ioctl_response.output_data
@@ -6,7 +6,7 @@ module RubySMB
6
6
  endian :little
7
7
  bit32 :protocol, label: 'Protocol ID Field', initial_value: RubySMB::SMB2::SMB2_PROTOCOL_ID
8
8
  uint16 :structure_size, label: 'Header Structure Size', initial_value: 64
9
- uint16 :credit_charge, label: 'Credit Charge', initial_value: 0
9
+ uint16 :credit_charge, label: 'Credit Charge', initial_value: 1
10
10
  nt_status :nt_status, label: 'NT Status', initial_value: 0
11
11
  uint16 :command, label: 'Command'
12
12
  uint16 :credits, label: 'Credit Request/Response'
@@ -23,12 +23,18 @@ module RubySMB
23
23
  # @return [Integer]
24
24
  attr_accessor :id
25
25
 
26
- def initialize(client:, share:, response:)
27
- @client = client
28
- @share = share
29
- @id = response.smb2_header.tree_id
30
- @permissions = response.maximal_access
31
- @share_type = response.share_type
26
+ # Whether or not the share associated with this tree connect needs to be encrypted (SMB 3.x)
27
+ # @!attribute [rw] tree_connect_encrypt_data
28
+ # @return [Boolean]
29
+ attr_accessor :tree_connect_encrypt_data
30
+
31
+ def initialize(client:, share:, response:, encrypt: false)
32
+ @client = client
33
+ @share = share
34
+ @id = response.smb2_header.tree_id
35
+ @permissions = response.maximal_access
36
+ @share_type = response.share_type
37
+ @tree_connect_encrypt_data = encrypt
32
38
  end
33
39
 
34
40
  # Disconnects this Tree from the current session
@@ -38,19 +44,26 @@ module RubySMB
38
44
  def disconnect!
39
45
  request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
40
46
  request = set_header_fields(request)
41
- raw_response = client.send_recv(request)
47
+ raw_response = client.send_recv(request, encrypt: @tree_connect_encrypt_data)
42
48
  response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
43
49
  unless response.valid?
44
50
  raise RubySMB::Error::InvalidPacket.new(
45
51
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
46
52
  expected_cmd: RubySMB::SMB2::Packet::TreeDisconnectResponse::COMMAND,
47
- received_proto: response.smb2_header.protocol,
48
- received_cmd: response.smb2_header.command
53
+ packet: response
49
54
  )
50
55
  end
51
56
  response.status_code
52
57
  end
53
58
 
59
+ def open_pipe(opts)
60
+ # Make sure we don't modify the caller's hash options
61
+ opts = opts.dup
62
+ opts[:filename] = opts[:filename].dup
63
+ opts[:filename] = opts[:filename][1..-1] if opts[:filename].start_with? '\\'
64
+ open_file(opts)
65
+ end
66
+
54
67
  def open_file(filename:, attributes: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
55
68
  impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)
56
69
 
@@ -96,26 +109,25 @@ module RubySMB
96
109
  create_request.create_disposition = disposition
97
110
  create_request.name = filename
98
111
 
99
- raw_response = client.send_recv(create_request)
112
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
100
113
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
101
114
  unless response.valid?
102
115
  raise RubySMB::Error::InvalidPacket.new(
103
116
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
104
117
  expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
105
- received_proto: response.smb2_header.protocol,
106
- received_cmd: response.smb2_header.command
118
+ packet: response
107
119
  )
108
120
  end
109
121
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
110
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
122
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
111
123
  end
112
124
 
113
125
  case @share_type
114
- when 0x01
115
- RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
116
- when 0x02
126
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
127
+ RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
128
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
117
129
  RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
118
- # when 0x03
130
+ # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
119
131
  # it's a printer!
120
132
  else
121
133
  raise RubySMB::Error::RubySMBError, 'Unsupported share type'
@@ -141,21 +153,27 @@ module RubySMB
141
153
  directory_request.file_information_class = type::CLASS_LEVEL
142
154
  directory_request.file_id = file_id
143
155
  directory_request.name = pattern
144
- directory_request.output_length = 65_535
156
+
157
+ max_read = client.server_max_read_size
158
+ max_read = 65536 unless client.server_supports_multi_credit
159
+ credit_charge = 0
160
+ if client.server_supports_multi_credit
161
+ credit_charge = (max_read - 1) / 65536 + 1
162
+ end
163
+ directory_request.output_length = max_read
164
+ directory_request.smb2_header.credit_charge = credit_charge
145
165
 
146
166
  directory_request = set_header_fields(directory_request)
147
167
 
148
168
  files = []
149
-
150
169
  loop do
151
- response = client.send_recv(directory_request)
170
+ response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
152
171
  directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
153
172
  unless directory_response.valid?
154
173
  raise RubySMB::Error::InvalidPacket.new(
155
174
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
156
175
  expected_cmd: RubySMB::SMB2::Packet::QueryDirectoryResponse::COMMAND,
157
- received_proto: directory_response.smb2_header.protocol,
158
- received_cmd: directory_response.smb2_header.command
176
+ packet: directory_response
159
177
  )
160
178
  end
161
179
 
@@ -164,7 +182,7 @@ module RubySMB
164
182
  break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
165
183
 
166
184
  unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
167
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
185
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
168
186
  end
169
187
 
170
188
  files += directory_response.results(type)
@@ -193,18 +211,17 @@ module RubySMB
193
211
 
194
212
  create_request = open_directory_packet(directory: directory, disposition: disposition,
195
213
  impersonation: impersonation, read: read, write: write, delete: delete)
196
- raw_response = client.send_recv(create_request)
214
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
197
215
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
198
216
  unless response.valid?
199
217
  raise RubySMB::Error::InvalidPacket.new(
200
218
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
201
219
  expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
202
- received_proto: response.smb2_header.protocol,
203
- received_cmd: response.smb2_header.command
220
+ packet: response
204
221
  )
205
222
  end
206
223
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
207
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
224
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
208
225
  end
209
226
 
210
227
  response
@@ -250,7 +267,6 @@ module RubySMB
250
267
  # @return [RubySMB::SMB2::Packet] the modified packet.
251
268
  def set_header_fields(request)
252
269
  request.smb2_header.tree_id = id
253
- request.smb2_header.credit_charge = 1
254
270
  request.smb2_header.credits = 256
255
271
  request
256
272
  end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '2.0.4'.freeze
3
3
  end
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.platform = Gem::Platform::RUBY
27
27
  end
28
28
 
29
- spec.required_ruby_version = '>= 2.3.0'
29
+ spec.required_ruby_version = '>= 2.5'
30
30
 
31
31
  spec.add_development_dependency 'bundler'
32
32
  spec.add_development_dependency 'fivemat'
@@ -36,4 +36,6 @@ Gem::Specification.new do |spec|
36
36
  spec.add_runtime_dependency 'rubyntlm'
37
37
  spec.add_runtime_dependency 'windows_error'
38
38
  spec.add_runtime_dependency 'bindata'
39
+ spec.add_runtime_dependency 'openssl-ccm'
40
+ spec.add_runtime_dependency 'openssl-cmac'
39
41
  end
@@ -6,11 +6,68 @@ RSpec.describe RubySMB::Client do
6
6
  let(:username) { 'msfadmin' }
7
7
  let(:password) { 'msfpasswd' }
8
8
  subject(:client) { described_class.new(dispatcher, username: username, password: password) }
9
- let(:smb1_client) { described_class.new(dispatcher, smb2: false, username: username, password: password) }
10
- let(:smb2_client) { described_class.new(dispatcher, smb1: false, username: username, password: password) }
9
+ let(:smb1_client) { described_class.new(dispatcher, smb2: false, smb3: false, username: username, password: password) }
10
+ let(:smb2_client) { described_class.new(dispatcher, smb1: false, smb3: false, username: username, password: password) }
11
+ let(:smb3_client) { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }
11
12
  let(:empty_packet) { RubySMB::SMB1::Packet::EmptyPacket.new }
12
13
  let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
13
14
 
15
+ it { is_expected.to respond_to :dispatcher }
16
+ it { is_expected.to respond_to :domain }
17
+ it { is_expected.to respond_to :local_workstation }
18
+ it { is_expected.to respond_to :ntlm_client }
19
+ it { is_expected.to respond_to :password }
20
+ it { is_expected.to respond_to :peer_native_os }
21
+ it { is_expected.to respond_to :peer_native_lm }
22
+ it { is_expected.to respond_to :primary_domain }
23
+ it { is_expected.to respond_to :default_name }
24
+ it { is_expected.to respond_to :default_domain }
25
+ it { is_expected.to respond_to :dns_host_name }
26
+ it { is_expected.to respond_to :dns_domain_name }
27
+ it { is_expected.to respond_to :dns_tree_name }
28
+ it { is_expected.to respond_to :os_version }
29
+ it { is_expected.to respond_to :dialect }
30
+ it { is_expected.to respond_to :sequence_counter }
31
+ it { is_expected.to respond_to :session_id }
32
+ it { is_expected.to respond_to :signing_required }
33
+ it { is_expected.to respond_to :smb1 }
34
+ it { is_expected.to respond_to :smb2 }
35
+ it { is_expected.to respond_to :smb3 }
36
+ it { is_expected.to respond_to :smb2_message_id }
37
+ it { is_expected.to respond_to :username }
38
+ it { is_expected.to respond_to :user_id }
39
+ it { is_expected.to respond_to :max_buffer_size }
40
+ it { is_expected.to respond_to :server_max_buffer_size }
41
+ it { is_expected.to respond_to :server_max_write_size }
42
+ it { is_expected.to respond_to :server_max_read_size }
43
+ it { is_expected.to respond_to :server_max_transact_size }
44
+ it { is_expected.to respond_to :preauth_integrity_hash_algorithm }
45
+ it { is_expected.to respond_to :preauth_integrity_hash_value }
46
+ it { is_expected.to respond_to :encryption_algorithm }
47
+ it { is_expected.to respond_to :client_encryption_key }
48
+ it { is_expected.to respond_to :server_encryption_key }
49
+ it { is_expected.to respond_to :session_encrypt_data }
50
+ it { is_expected.to respond_to :server_encryption_algorithms }
51
+ it { is_expected.to respond_to :server_compression_algorithms }
52
+ it { is_expected.to respond_to :negotiated_smb_version }
53
+ it { is_expected.to respond_to :session_key }
54
+ it { is_expected.to respond_to :tree_connects }
55
+ it { is_expected.to respond_to :open_files }
56
+ it { is_expected.to respond_to :use_ntlmv2 }
57
+ it { is_expected.to respond_to :usentlm2_session }
58
+ it { is_expected.to respond_to :send_lm }
59
+ it { is_expected.to respond_to :use_lanman_key }
60
+ it { is_expected.to respond_to :send_ntlm }
61
+ it { is_expected.to respond_to :spnopt }
62
+ it { is_expected.to respond_to :evasion_opts }
63
+ it { is_expected.to respond_to :native_os }
64
+ it { is_expected.to respond_to :native_lm }
65
+ it { is_expected.to respond_to :verify_signature }
66
+ it { is_expected.to respond_to :auth_user }
67
+ it { is_expected.to respond_to :last_file_id }
68
+ it { is_expected.to respond_to :pid }
69
+ it { is_expected.to respond_to :server_supports_multi_credit }
70
+
14
71
  describe '#initialize' do
15
72
  it 'should raise an ArgumentError without a valid dispatcher' do
16
73
  expect { described_class.new(nil) }.to raise_error(ArgumentError)
@@ -26,14 +83,21 @@ RSpec.describe RubySMB::Client do
26
83
 
27
84
  it 'accepts an argument to disable smb1 support' do
28
85
  expect(smb2_client.smb1).to be false
86
+ expect(smb3_client.smb1).to be false
29
87
  end
30
88
 
31
89
  it 'accepts an argument to disable smb2 support' do
32
90
  expect(smb1_client.smb2).to be false
91
+ expect(smb3_client.smb2).to be false
92
+ end
93
+
94
+ it 'accepts an argument to disable smb3 support' do
95
+ expect(smb1_client.smb3).to be false
96
+ expect(smb2_client.smb3).to be false
33
97
  end
34
98
 
35
- it 'raises an exception if both SMB1 and SMB2 are disabled' do
36
- expect { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
99
+ it 'raises an exception if SMB1, SMB2 and SMB3 are disabled' do
100
+ expect { described_class.new(dispatcher, smb1: false, smb2: false, smb3: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
37
101
  end
38
102
 
39
103
  it 'sets the username attribute' do
@@ -44,6 +108,11 @@ RSpec.describe RubySMB::Client do
44
108
  expect(client.password).to eq password
45
109
  end
46
110
 
111
+ it 'sets the session_encrypt_data attribute' do
112
+ client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
113
+ expect(client.session_encrypt_data).to eq true
114
+ end
115
+
47
116
  it 'creates an NTLM client' do
48
117
  expect(client.ntlm_client).to be_a Net::NTLM::Client
49
118
  end
@@ -58,7 +127,7 @@ RSpec.describe RubySMB::Client do
58
127
  expect(opt[:workstation]).to eq(local_workstation)
59
128
  expect(opt[:domain]).to eq(domain)
60
129
  flags = Net::NTLM::Client::DEFAULT_FLAGS |
61
- Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000
130
+ Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000 ^ Net::NTLM::FLAGS[:OEM]
62
131
  expect(opt[:flags]).to eq(flags)
63
132
  end
64
133
 
@@ -74,30 +143,367 @@ RSpec.describe RubySMB::Client do
74
143
  it 'sets the max_buffer_size to MAX_BUFFER_SIZE' do
75
144
  expect(client.max_buffer_size).to eq RubySMB::Client::MAX_BUFFER_SIZE
76
145
  end
146
+
147
+ it 'sets the server_supports_multi_credit to false' do
148
+ expect(client.server_supports_multi_credit).to be false
149
+ end
150
+
151
+ it 'sets the pid to a random value' do
152
+ 100.times do
153
+ previous_pid = client.pid
154
+ client = described_class.new(dispatcher, username: username, password: password)
155
+ expect(client.pid).to_not eq(previous_pid)
156
+ end
157
+ end
158
+ end
159
+
160
+ describe '#echo' do
161
+ let(:response) { double('Echo Response') }
162
+ before :example do
163
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
164
+ end
165
+
166
+ context 'with SMB1' do
167
+ it 'calls #smb1_echo with the expected arguments' do
168
+ allow(smb1_client).to receive(:smb1_echo).and_return(response)
169
+ count = 3
170
+ data = 'testing...'
171
+ smb1_client.echo(count: count, data: data)
172
+ expect(smb1_client).to have_received(:smb1_echo).with(count: count, data: data)
173
+ end
174
+ end
175
+
176
+ context 'with SMB2' do
177
+ it 'calls #smb2_echo without arguments' do
178
+ allow(smb2_client).to receive(:smb2_echo).and_return(response)
179
+ smb2_client.echo
180
+ expect(smb2_client).to have_received(:smb2_echo)
181
+ end
182
+ end
183
+
184
+ context 'with SMB3' do
185
+ it 'calls #smb2_echo without arguments' do
186
+ allow(smb3_client).to receive(:smb2_echo).and_return(response)
187
+ smb3_client.echo
188
+ expect(smb3_client).to have_received(:smb2_echo)
189
+ end
190
+ end
191
+
192
+ it 'returns the expected status code' do
193
+ allow(smb2_client).to receive(:smb2_echo).and_return(response)
194
+ expect(smb2_client.echo).to eq(WindowsError::NTStatus::STATUS_SUCCESS)
195
+ end
77
196
  end
78
197
 
79
198
  describe '#send_recv' do
80
199
  let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
81
200
  let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
201
+ let(:smb2_header) { RubySMB::SMB2::SMB2Header.new }
82
202
 
83
203
  before(:each) do
84
- expect(dispatcher).to receive(:send_packet).and_return(nil)
85
- expect(dispatcher).to receive(:recv_packet).and_return('A')
204
+ allow(client).to receive(:is_status_pending?).and_return(false)
205
+ allow(dispatcher).to receive(:send_packet).and_return(nil)
206
+ allow(dispatcher).to receive(:recv_packet).and_return('A')
207
+ allow(RubySMB::SMB2::SMB2Header).to receive(:read).and_return(smb2_header)
86
208
  end
87
209
 
88
- it 'checks the packet version' do
89
- expect(smb1_request).to receive(:packet_smb_version).and_call_original
90
- client.send_recv(smb1_request)
210
+ context 'when signing' do
211
+ it 'calls #smb1_sign if it is an SMB1 packet' do
212
+ expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
213
+ client.send_recv(smb1_request)
214
+ end
215
+
216
+ context 'with an SMB2 packet' do
217
+ it 'does not sign a SessionSetupRequest packet' do
218
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
219
+ expect(smb2_client).to_not receive(:smb2_sign)
220
+ expect(smb2_client).to_not receive(:smb3_sign)
221
+ smb2_client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
222
+ end
223
+
224
+ it 'calls #smb2_sign if it is an SMB2 client' do
225
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
226
+ expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
227
+ smb2_client.send_recv(smb2_request)
228
+ end
229
+
230
+ it 'calls #smb3_sign if it is an SMB3 client' do
231
+ allow(smb3_client).to receive(:is_status_pending?).and_return(false)
232
+ expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
233
+ smb3_client.send_recv(smb2_request)
234
+ end
235
+ end
91
236
  end
92
237
 
93
- it 'calls #smb1_sign if it is an SMB1 packet' do
94
- expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
238
+ it 'sends the expected packet and gets the response' do
239
+ expect(dispatcher).to receive(:send_packet).with(smb1_request)
240
+ expect(dispatcher).to receive(:recv_packet)
95
241
  client.send_recv(smb1_request)
96
242
  end
97
243
 
98
- it 'calls #smb2_sign if it is an SMB2 packet' do
99
- expect(client).to receive(:smb2_sign).with(smb2_request).and_call_original
100
- client.send_recv(smb2_request)
244
+ context 'with SMB1' do
245
+ it 'does not check if it is a STATUS_PENDING response' do
246
+ expect(smb1_client).to_not receive(:is_status_pending?)
247
+ smb1_client.send_recv(smb1_request)
248
+ end
249
+
250
+ it 'set the #uid SMB header when #user_id is defined' do
251
+ smb1_client.user_id = 333
252
+ smb1_client.send_recv(smb1_request)
253
+ expect(smb1_request.smb_header.uid).to eq(333)
254
+ end
255
+
256
+ it 'does not set the #uid SMB header when #user_id is not defined' do
257
+ smb1_client.send_recv(smb1_request)
258
+ expect(smb1_request.smb_header.uid).to eq(0)
259
+ end
260
+
261
+ it 'set the #pid SMB header when #pid is defined' do
262
+ smb1_client.pid = 333
263
+ smb1_client.send_recv(smb1_request)
264
+ expect(smb1_request.smb_header.pid_low).to eq(333)
265
+ end
266
+
267
+ it 'does not set the #pid SMB header when #pid is not defined' do
268
+ smb1_client.pid = nil
269
+ smb1_client.send_recv(smb1_request)
270
+ expect(smb1_request.smb_header.pid_low).to eq(0)
271
+ end
272
+ end
273
+
274
+ context 'with SMB2' do
275
+ context 'when receiving a STATUS_PENDING response' do
276
+ it 'waits 1 second and reads/decrypts again' do
277
+ allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
278
+ expect(smb2_client).to receive(:sleep).with(1)
279
+ expect(dispatcher).to receive(:recv_packet).twice
280
+ smb2_client.send_recv(smb2_request)
281
+ end
282
+ end
283
+ end
284
+
285
+ context 'with SMB3 and encryption' do
286
+ before :example do
287
+ smb3_client.dialect = '0x0300'
288
+ allow(smb3_client).to receive(:is_status_pending?).and_return(false)
289
+ end
290
+
291
+ context 'with a SessionSetupRequest' do
292
+ it 'does not encrypt/decrypt' do
293
+ request = RubySMB::SMB2::Packet::SessionSetupRequest.new
294
+ expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
295
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
296
+ smb3_client.send_recv(request)
297
+ end
298
+ end
299
+
300
+ context 'with a NegotiateRequest' do
301
+ it 'does not encrypt/decrypt' do
302
+ request = RubySMB::SMB2::Packet::NegotiateRequest.new
303
+ expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
304
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
305
+ smb3_client.send_recv(request)
306
+ end
307
+ end
308
+
309
+ it 'encrypts and decrypts' do
310
+ expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
311
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: true)
312
+ smb3_client.send_recv(smb2_request)
313
+ end
314
+
315
+ context 'when receiving a STATUS_PENDING response' do
316
+ it 'waits 1 second and reads/decrypts again' do
317
+ allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
318
+ expect(smb3_client).to receive(:sleep).with(1)
319
+ expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
320
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: true).twice
321
+ smb3_client.send_recv(smb2_request)
322
+ end
323
+ end
324
+ end
325
+
326
+ it 'increments the sequence counter if signing is required and the session key exist' do
327
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
328
+ smb2_client.signing_required = true
329
+ smb2_client.session_key = 'key'
330
+ smb2_client.sequence_counter = 0
331
+ smb2_client.send_recv(smb2_request)
332
+ expect(smb2_client.sequence_counter).to eq(1)
333
+ end
334
+
335
+ it 'updates #smb2_message_id with SMB2 header #credit_charge if the server supports multi credits' do
336
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
337
+ smb2_client.smb2_message_id = 0
338
+ smb2_client.server_supports_multi_credit = true
339
+ smb2_header.credit_charge = 5
340
+ smb2_client.send_recv(smb2_request)
341
+ expect(smb2_client.smb2_message_id).to eq(5)
342
+ end
343
+
344
+ it 'does not update #msb2_message_id with SMB2 header #credit_charge if the server does not support multi credits' do
345
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
346
+ smb2_client.smb2_message_id = 0
347
+ smb2_client.server_supports_multi_credit = false
348
+ smb2_header.credit_charge = 5
349
+ smb2_client.send_recv(smb2_request)
350
+ expect(smb2_client.smb2_message_id).to eq(1)
351
+ end
352
+
353
+ it 'ignores errors thrown when parsing the SMB2 header' do
354
+ allow(RubySMB::SMB2::SMB2Header).to receive(:read).and_raise(IOError)
355
+ expect { smb2_client.send_recv(smb2_request) }.to_not raise_error
356
+ end
357
+ end
358
+
359
+ describe '#is_status_pending?' do
360
+ let(:response) {
361
+ res = RubySMB::SMB2::Packet::SessionSetupRequest.new
362
+ res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
363
+ res.smb2_header.flags.async_command = 1
364
+ res
365
+ }
366
+
367
+ it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
368
+ expect(client.is_status_pending?(response.smb2_header)).to be true
369
+ end
370
+
371
+ it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
372
+ response.smb2_header.flags.async_command = 0
373
+ expect(client.is_status_pending?(response.smb2_header)).to be false
374
+ end
375
+
376
+ it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
377
+ response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
378
+ expect(client.is_status_pending?(response.smb2_header)).to be false
379
+ end
380
+ end
381
+
382
+ describe '#can_be_encrypted?' do
383
+ it 'returns true if the packet can be encrypted' do
384
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
385
+ expect(client.can_be_encrypted?(packet)).to be true
386
+ end
387
+
388
+ it 'returns false if it is an SMB1 packet' do
389
+ packet = RubySMB::SMB1::Packet::LogoffRequest.new
390
+ expect(client.can_be_encrypted?(packet)).to be false
391
+ end
392
+
393
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
394
+ it "returns false if the packet is a #{klass}" do
395
+ packet = klass.new
396
+ expect(client.can_be_encrypted?(packet)).to be false
397
+ end
398
+ end
399
+ end
400
+
401
+ describe '#encryption_supported?' do
402
+ ['0x0300', '0x0302', '0x0311'].each do |dialect|
403
+ it "returns true if the dialect is #{dialect}" do
404
+ client.dialect = dialect
405
+ expect(client.encryption_supported?).to be true
406
+ end
407
+ end
408
+
409
+ it "returns false if the dialect does not support encryption" do
410
+ client.dialect = '0x0202'
411
+ expect(client.encryption_supported?).to be false
412
+ end
413
+ end
414
+
415
+ describe '#send_packet' do
416
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
417
+ before :example do
418
+ allow(dispatcher).to receive(:send_packet)
419
+ client.dialect = '0x0300'
420
+ end
421
+
422
+ it 'does not encrypt the packet' do
423
+ expect(client).to_not receive(:smb3_encrypt)
424
+ client.send_packet(packet)
425
+ end
426
+
427
+ it 'sends the packet through the dispatcher' do
428
+ client.send_packet(packet)
429
+ expect(dispatcher).to have_received(:send_packet).with(packet)
430
+ end
431
+
432
+ context 'with encryption' do
433
+ it 'creates a Transform request' do
434
+ expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
435
+ client.send_packet(packet, encrypt: true)
436
+ end
437
+
438
+ it 'raises an EncryptionError exception if an error occurs while encrypting' do
439
+ allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
440
+ expect { client.send_packet(packet, encrypt: true) }.to raise_error(
441
+ RubySMB::Error::EncryptionError,
442
+ "Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
443
+ )
444
+ end
445
+
446
+ it 'sends the encrypted packet' do
447
+ encrypted_packet = double('Encrypted packet')
448
+ allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
449
+ client.send_packet(packet, encrypt: true)
450
+ expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
451
+ end
452
+ end
453
+ end
454
+
455
+ describe '#recv_packet' do
456
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
457
+ before :example do
458
+ allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
459
+ client.dialect = '0x0300'
460
+ allow(client).to receive(:smb3_decrypt)
461
+ end
462
+
463
+ it 'reads the response packet' do
464
+ client.recv_packet
465
+ expect(dispatcher).to have_received(:recv_packet)
466
+ end
467
+
468
+ it 'raises an CommunicationError exception if an error occurs while receiving the response' do
469
+ allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
470
+ expect { client.recv_packet }.to raise_error(RubySMB::Error::CommunicationError)
471
+ end
472
+
473
+ context 'with encryption' do
474
+ it 'raises an EncryptionError exception if an error occurs while receiving the response' do
475
+ allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
476
+ expect { client.recv_packet(encrypt: true) }.to raise_error(
477
+ RubySMB::Error::EncryptionError,
478
+ 'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
479
+ 'The server supports encryption but was not able to handle the encrypted request.'
480
+ )
481
+ end
482
+
483
+ it 'parses the response as a Transform response packet' do
484
+ expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
485
+ client.recv_packet(encrypt: true)
486
+ end
487
+
488
+ it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
489
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
490
+ expect { client.recv_packet(encrypt: true) }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
491
+ end
492
+
493
+ it 'decrypts the Transform response packet' do
494
+ transform = double('Transform header packet')
495
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
496
+ client.recv_packet(encrypt: true)
497
+ expect(client).to have_received(:smb3_decrypt).with(transform)
498
+ end
499
+
500
+ it 'raises an EncryptionError exception if an error occurs while decrypting' do
501
+ allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
502
+ expect { client.recv_packet(encrypt: true) }.to raise_error(
503
+ RubySMB::Error::EncryptionError,
504
+ 'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
505
+ )
506
+ end
101
507
  end
102
508
  end
103
509
 
@@ -240,6 +646,44 @@ RSpec.describe RubySMB::Client do
240
646
  expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
241
647
  end
242
648
  end
649
+
650
+ context 'with SMB3' do
651
+ let(:raw_response) { double('Raw response') }
652
+ let(:logoff_response) {
653
+ RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
654
+ }
655
+ before :example do
656
+ allow(smb3_client).to receive(:send_recv).and_return(raw_response)
657
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
658
+ allow(smb3_client).to receive(:wipe_state!)
659
+ end
660
+
661
+ it 'creates a LogoffRequest packet' do
662
+ expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
663
+ smb3_client.logoff!
664
+ end
665
+
666
+ it 'calls #send_recv' do
667
+ expect(smb3_client).to receive(:send_recv)
668
+ smb3_client.logoff!
669
+ end
670
+
671
+ it 'reads the raw response as a LogoffResponse packet' do
672
+ expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
673
+ smb3_client.logoff!
674
+ end
675
+
676
+ it 'raise an InvalidPacket exception when the response is an error packet' do
677
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
678
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
679
+ end
680
+
681
+ it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
682
+ logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
683
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
684
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
685
+ end
686
+ end
243
687
  end
244
688
 
245
689
  context 'NetBIOS Session Service' do
@@ -310,7 +754,7 @@ RSpec.describe RubySMB::Client do
310
754
  expect(session_packet.session_header.session_packet_type).to eq RubySMB::Nbss::SESSION_REQUEST
311
755
  expect(session_packet.called_name).to eq called_name
312
756
  expect(session_packet.calling_name).to eq calling_name
313
- expect(session_packet.session_header.packet_length).to eq(
757
+ expect(session_packet.session_header.stream_protocol_length).to eq(
314
758
  session_packet.called_name.to_binary_s.size + session_packet.calling_name.to_binary_s.size
315
759
  )
316
760
  end
@@ -365,7 +809,8 @@ RSpec.describe RubySMB::Client do
365
809
  smb1_extended_response.to_binary_s
366
810
  }
367
811
 
368
- let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
812
+ let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
813
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
369
814
 
370
815
  describe '#smb1_negotiate_request' do
371
816
  it 'returns an SMB1 Negotiate Request packet' do
@@ -373,33 +818,158 @@ RSpec.describe RubySMB::Client do
373
818
  end
374
819
 
375
820
  it 'sets the default SMB1 Dialect' do
376
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
821
+ expect(client.smb1_negotiate_request.dialects).to include(
822
+ buffer_format: 2,
823
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
824
+ )
377
825
  end
378
826
 
379
827
  it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
380
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
828
+ expect(client.smb1_negotiate_request.dialects).to include(
829
+ buffer_format: 2,
830
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
831
+ )
381
832
  end
382
833
 
383
834
  it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
384
- expect(smb1_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
835
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
836
+ buffer_format: 2,
837
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
838
+ )
385
839
  end
386
840
 
387
841
  it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
388
- expect(smb2_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
842
+ expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
843
+ buffer_format: 2,
844
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
845
+ )
846
+ end
847
+
848
+ it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
849
+ expect(client.smb1_negotiate_request.dialects).to include(
850
+ buffer_format: 2,
851
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
852
+ )
853
+ end
854
+
855
+ it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
856
+ expect(smb3_client.smb1_negotiate_request.dialects).to include(
857
+ buffer_format: 2,
858
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
859
+ )
860
+ end
861
+
862
+ it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
863
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
864
+ buffer_format: 2,
865
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
866
+ )
389
867
  end
390
868
  end
391
869
 
392
- describe '#smb2_negotiate_request' do
870
+ describe '#smb2_3_negotiate_request' do
393
871
  it 'return an SMB2 Negotiate Request packet' do
394
- expect(client.smb2_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
872
+ expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
395
873
  end
396
874
 
397
- it 'sets the default SMB2 Dialect' do
398
- expect(client.smb2_negotiate_request.dialects).to include(RubySMB::Client::SMB2_DIALECT_DEFAULT)
875
+ it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
876
+ expect(client.smb2_3_negotiate_request.dialects).to include(
877
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
878
+ )
879
+ end
880
+
881
+ it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
882
+ expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
883
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
884
+ )
399
885
  end
400
886
 
401
887
  it 'sets the Message ID to 0' do
402
- expect(client.smb2_negotiate_request.smb2_header.message_id).to eq 0
888
+ expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
889
+ end
890
+
891
+ it 'adds SMB3 dialects if if SMB3 support is enabled' do
892
+ expect(client.smb2_3_negotiate_request.dialects).to include(
893
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
894
+ )
895
+ end
896
+
897
+ it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
898
+ expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
899
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
900
+ )
901
+ end
902
+ end
903
+
904
+ describe '#add_smb3_to_negotiate_request' do
905
+ let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
906
+
907
+ it 'adds the default SMB3 dialects' do
908
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
909
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
910
+ )
911
+ end
912
+
913
+ it 'raises the expected exception when the dialects is not an array of strings' do
914
+ dialects = ['0x0300', 0x0302, '0x0311']
915
+ expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
916
+ end
917
+
918
+ it 'sets encryption capability flag' do
919
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
920
+ end
921
+
922
+ context 'when the negotiate packet includes the 0x0311 dialect' do
923
+ before :example do
924
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
925
+ end
926
+
927
+ it 'adds 3 Negotiate Contexts' do
928
+ expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
929
+ end
930
+
931
+ it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
932
+ nc = negotiate_request.negotiate_context_list.select do |n|
933
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
934
+ end
935
+ expect(nc.length).to eq(1)
936
+ expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
937
+ end
938
+
939
+ it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
940
+ nc = negotiate_request.negotiate_context_list.select do |n|
941
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
942
+ end
943
+ expect(nc.length).to eq(1)
944
+ expect(nc.first.data.ciphers).to eq(
945
+ [
946
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
947
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
948
+ ]
949
+ )
950
+ end
951
+
952
+ it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
953
+ nc = negotiate_request.negotiate_context_list.select do |n|
954
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
955
+ end
956
+ expect(nc.length).to eq(1)
957
+ expect(nc.first.data.compression_algorithms).to eq(
958
+ [
959
+ RubySMB::SMB2::CompressionCapabilities::LZNT1,
960
+ RubySMB::SMB2::CompressionCapabilities::LZ77,
961
+ RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
962
+ RubySMB::SMB2::CompressionCapabilities::Pattern_V1
963
+ ]
964
+ )
965
+ end
966
+ end
967
+
968
+ context 'when the negotiate packet does not include the 0x0311 dialect' do
969
+ it 'does not add any Negotiate Context' do
970
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
971
+ expect(negotiate_request.negotiate_context_list?). to be false
972
+ end
403
973
  end
404
974
  end
405
975
 
@@ -414,10 +984,15 @@ RSpec.describe RubySMB::Client do
414
984
  client.negotiate_request
415
985
  end
416
986
 
417
- it 'calls #smb2_negotiate_request if SMB2 is enabled' do
418
- expect(smb2_client).to receive(:smb2_negotiate_request)
987
+ it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
988
+ expect(smb2_client).to receive(:smb2_3_negotiate_request)
419
989
  smb2_client.negotiate_request
420
990
  end
991
+
992
+ it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
993
+ expect(smb3_client).to receive(:smb2_3_negotiate_request)
994
+ smb3_client.negotiate_request
995
+ end
421
996
  end
422
997
 
423
998
  describe '#negotiate_response' do
@@ -464,12 +1039,28 @@ RSpec.describe RubySMB::Client do
464
1039
  end
465
1040
  end
466
1041
 
467
- context 'with SMB1 and SMB2 enabled' do
1042
+ context 'with only SMB3' do
1043
+ it 'returns a properly formed packet' do
1044
+ expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
1045
+ end
1046
+
1047
+ it 'raises an exception if the Response is invalid' do
1048
+ expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
1049
+ end
1050
+
1051
+ it 'considers the response invalid if it is not an actual Negotiate Response' do
1052
+ bogus_response = smb2_response
1053
+ bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
1054
+ expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
1055
+ end
1056
+ end
1057
+
1058
+ context 'with SMB1, SMB2 and SMB3 enabled' do
468
1059
  it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
469
1060
  expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
470
1061
  end
471
1062
 
472
- it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
1063
+ it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
473
1064
  expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
474
1065
  end
475
1066
  end
@@ -477,9 +1068,10 @@ RSpec.describe RubySMB::Client do
477
1068
 
478
1069
  describe '#parse_negotiate_response' do
479
1070
  context 'when SMB1 was Negotiated' do
480
- it 'turns off SMB2 support' do
1071
+ it 'turns off SMB2 and SMB3 support' do
481
1072
  client.parse_negotiate_response(smb1_extended_response)
482
1073
  expect(client.smb2).to be false
1074
+ expect(client.smb3).to be false
483
1075
  end
484
1076
 
485
1077
  it 'sets whether or not signing is required' do
@@ -502,12 +1094,18 @@ RSpec.describe RubySMB::Client do
502
1094
  it 'returns the string \'SMB1\'' do
503
1095
  expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
504
1096
  end
1097
+
1098
+ it 'sets #negotiated_smb_version to 1' do
1099
+ client.parse_negotiate_response(smb1_extended_response)
1100
+ expect(client.negotiated_smb_version).to eq(1)
1101
+ end
505
1102
  end
506
1103
 
507
1104
  context 'when SMB2 was negotiated' do
508
- it 'turns off SMB1 support' do
1105
+ it 'turns off SMB1 and SMB3 support' do
509
1106
  client.parse_negotiate_response(smb2_response)
510
1107
  expect(client.smb1).to be false
1108
+ expect(client.smb3).to be false
511
1109
  end
512
1110
 
513
1111
  it 'sets whether or not signing is required' do
@@ -525,10 +1123,142 @@ RSpec.describe RubySMB::Client do
525
1123
  it 'returns the string \'SMB2\'' do
526
1124
  expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
527
1125
  end
1126
+
1127
+ it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
1128
+ smb2_response.capabilities.large_mtu = 1
1129
+ client.parse_negotiate_response(smb2_response)
1130
+ expect(client.server_supports_multi_credit).to be true
1131
+ end
1132
+
1133
+ it 'sets #server_supports_multi_credit to false when the dialect is 0x0202' do
1134
+ smb2_response.dialect_revision = 0x0202
1135
+ smb2_response.capabilities.large_mtu = 1 # just to make sure it won't affect the result
1136
+ client.parse_negotiate_response(smb2_response)
1137
+ expect(client.server_supports_multi_credit).to be false
1138
+ end
1139
+ end
1140
+
1141
+ context 'when SMB3 was negotiated' do
1142
+ it 'turns off SMB1 and SMB2 support' do
1143
+ client.parse_negotiate_response(smb3_response)
1144
+ expect(client.smb1).to be false
1145
+ expect(client.smb2).to be false
1146
+ end
1147
+
1148
+ it 'sets whether or not signing is required' do
1149
+ smb3_response.security_mode.signing_required = 1
1150
+ client.parse_negotiate_response(smb3_response)
1151
+ expect(client.signing_required).to be true
1152
+ end
1153
+
1154
+ it 'sets #dialect to the negotiated dialect' do
1155
+ client.parse_negotiate_response(smb3_response)
1156
+ expect(client.dialect).to eq '0x0300'
1157
+ end
1158
+
1159
+ it 'returns the string \'SMB2\'' do
1160
+ expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
1161
+ end
1162
+
1163
+ it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
1164
+ smb3_response.capabilities.large_mtu = 1
1165
+ client.parse_negotiate_response(smb3_response)
1166
+ expect(client.server_supports_multi_credit).to be true
1167
+ end
1168
+
1169
+ context 'when the server supports encryption' do
1170
+ before :example do
1171
+ smb3_response.capabilities.encryption = 1
1172
+ end
1173
+
1174
+ it 'sets the expected encryption algorithm' do
1175
+ client.parse_negotiate_response(smb3_response)
1176
+ expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
1177
+ end
1178
+
1179
+ it 'keeps session encryption enabled if it was already' do
1180
+ client.session_encrypt_data = true
1181
+ client.parse_negotiate_response(smb3_response)
1182
+ expect(client.session_encrypt_data).to be true
1183
+ end
1184
+
1185
+ it 'keeps session encryption disabled if it was already' do
1186
+ client.session_encrypt_data = false
1187
+ client.parse_negotiate_response(smb3_response)
1188
+ expect(client.session_encrypt_data).to be false
1189
+ end
1190
+ end
1191
+
1192
+ context 'when the server does not support encryption' do
1193
+ before :example do
1194
+ smb3_response.capabilities.encryption = 0
1195
+ end
1196
+
1197
+ it 'disables session encryption if it was already enabled' do
1198
+ client.session_encrypt_data = true
1199
+ client.parse_negotiate_response(smb3_response)
1200
+ expect(client.session_encrypt_data).to be false
1201
+ end
1202
+
1203
+ it 'keeps session encryption disabled if it was already' do
1204
+ client.session_encrypt_data = false
1205
+ client.parse_negotiate_response(smb3_response)
1206
+ expect(client.session_encrypt_data).to be false
1207
+ end
1208
+ end
1209
+ end
1210
+
1211
+ context 'when the response contains the SMB2 wildcard revision number dialect' do
1212
+ it 'only turns off SMB1 support' do
1213
+ smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
1214
+ client.parse_negotiate_response(smb2_response)
1215
+ expect(client.smb1).to be false
1216
+ expect(client.smb2).to be true
1217
+ expect(client.smb3).to be true
1218
+ end
1219
+ end
1220
+
1221
+ context 'when the negotiation failed' do
1222
+ context 'with a STATUS_NOT_SUPPORTED status code' do
1223
+ before :example do
1224
+ error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
1225
+ end
1226
+
1227
+ it 'raises the expected exception with SMB2' do
1228
+ expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
1229
+ RubySMB::Error::NegotiationFailure,
1230
+ 'Unable to negotiate with remote host, SMB2 not supported'
1231
+ )
1232
+ end
1233
+
1234
+ it 'raises the expected exception with SMB3' do
1235
+ expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
1236
+ RubySMB::Error::NegotiationFailure,
1237
+ 'Unable to negotiate with remote host, SMB3 not supported'
1238
+ )
1239
+ end
1240
+ end
1241
+
1242
+ context 'with an unknown status code' do
1243
+ it 'raises the expected exception' do
1244
+ expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
1245
+ RubySMB::Error::NegotiationFailure,
1246
+ 'Unable to negotiate with remote host'
1247
+ )
1248
+ end
1249
+ end
528
1250
  end
529
1251
  end
530
1252
 
531
1253
  describe '#negotiate' do
1254
+ let(:request_packet) { client.smb1_negotiate_request }
1255
+ before :example do
1256
+ allow(client).to receive(:negotiate_request)
1257
+ allow(client).to receive(:send_recv)
1258
+ allow(client).to receive(:negotiate_response)
1259
+ allow(client).to receive(:parse_negotiate_response)
1260
+ end
1261
+
532
1262
  it 'calls the backing methods' do
533
1263
  expect(client).to receive(:negotiate_request)
534
1264
  expect(client).to receive(:send_recv)
@@ -537,18 +1267,247 @@ RSpec.describe RubySMB::Client do
537
1267
  client.negotiate
538
1268
  end
539
1269
 
540
- it 'sets the response-packet #dialects array with the dialects sent in the request' do
541
- request_packet = client.smb1_negotiate_request
542
- allow(client).to receive(:negotiate_request).and_return(request_packet)
543
- allow(client).to receive(:send_recv)
544
- allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
545
- expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
546
- client.negotiate
1270
+ context 'with SMB1' do
1271
+ it 'sets the response-packet #dialects array with the dialects sent in the request' do
1272
+ request_packet = client.smb1_negotiate_request
1273
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1274
+ allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
1275
+ expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
1276
+ client.negotiate
1277
+ end
547
1278
  end
548
1279
 
549
- it 'raise the expected exception if an error occurs' do
550
- allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
551
- expect { client.negotiate }.to raise_error(RubySMB::Error::NegotiationFailure)
1280
+ context "with 0x0311 dialect" do
1281
+ it 'calls #parse_negotiate_response and updates the preauth hash' do
1282
+ client.dialect = '0x0311'
1283
+ request_packet = client.smb2_3_negotiate_request
1284
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1285
+ allow(client).to receive(:negotiate_response).and_return(smb3_response)
1286
+ expect(client).to receive(:parse_negotiate_response).with(smb3_response)
1287
+ expect(client).to receive(:update_preauth_hash).with(request_packet)
1288
+ expect(client).to receive(:update_preauth_hash).with(smb3_response)
1289
+ client.negotiate
1290
+ end
1291
+ end
1292
+
1293
+ context 'with a wildcard revision number response' do
1294
+ before :example do
1295
+ client.dialect = '0x02ff'
1296
+ allow(client).to receive(:smb2_message_id=) do
1297
+ client.dialect = '0x0202'
1298
+ end
1299
+ end
1300
+
1301
+ it 'increments the message ID' do
1302
+ expect(client).to receive(:smb2_message_id=).with(1)
1303
+ client.negotiate
1304
+ end
1305
+
1306
+ it 're-negotiates' do
1307
+ expect(client).to receive(:negotiate_request).twice
1308
+ expect(client).to receive(:send_recv).twice
1309
+ expect(client).to receive(:negotiate_response).twice
1310
+ expect(client).to receive(:parse_negotiate_response).twice
1311
+ client.negotiate
1312
+ end
1313
+ end
1314
+
1315
+ context 'when an error occurs' do
1316
+ before :example do
1317
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1318
+ allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
1319
+ client.smb1 = false
1320
+ client.smb2 = false
1321
+ client.smb3 = false
1322
+ end
1323
+
1324
+ context 'with SMB1' do
1325
+ let(:request_packet) { client.smb1_negotiate_request }
1326
+
1327
+ it 'raise the expected exception' do
1328
+ client.smb1 = true
1329
+ expect { client.negotiate }.to raise_error(
1330
+ RubySMB::Error::NegotiationFailure,
1331
+ "Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
1332
+ )
1333
+ end
1334
+ end
1335
+
1336
+ context 'with SMB2' do
1337
+ let(:request_packet) { client.smb2_3_negotiate_request }
1338
+
1339
+ it 'raise the expected exception' do
1340
+ client.smb2 = true
1341
+ expect { client.negotiate }.to raise_error(
1342
+ RubySMB::Error::NegotiationFailure,
1343
+ "Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
1344
+ )
1345
+ end
1346
+ end
1347
+
1348
+ context 'with SMB3' do
1349
+ let(:request_packet) { client.smb2_3_negotiate_request }
1350
+
1351
+ it 'raise the expected exception' do
1352
+ client.smb3 = true
1353
+ expect { client.negotiate }.to raise_error(
1354
+ RubySMB::Error::NegotiationFailure,
1355
+ "Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
1356
+ )
1357
+ end
1358
+ end
1359
+ end
1360
+
1361
+ describe '#parse_smb3_capabilities' do
1362
+ let(:request_packet) { client.smb2_3_negotiate_request }
1363
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
1364
+ let(:nc_encryption) do
1365
+ nc = RubySMB::SMB2::NegotiateContext.new(
1366
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1367
+ )
1368
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
1369
+ nc
1370
+ end
1371
+ let(:nc_integrity) do
1372
+ nc = RubySMB::SMB2::NegotiateContext.new(
1373
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1374
+ )
1375
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
1376
+ nc
1377
+ end
1378
+
1379
+ before :example do
1380
+ allow(smb3_client).to receive(:update_preauth_hash)
1381
+ smb3_response.add_negotiate_context(nc_encryption)
1382
+ smb3_response.add_negotiate_context(nc_integrity)
1383
+ end
1384
+
1385
+ context 'when selecting the integrity hash algorithm' do
1386
+ context 'with one algorithm' do
1387
+ it 'selects the expected algorithm' do
1388
+ smb3_client.parse_smb3_capabilities(smb3_response)
1389
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1390
+ end
1391
+ end
1392
+
1393
+ context 'with multiple algorithms' do
1394
+ it 'selects the first algorithm' do
1395
+ nc = smb3_response.find_negotiate_context(
1396
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1397
+ )
1398
+ nc.data.hash_algorithms << 3
1399
+ smb3_client.parse_smb3_capabilities(smb3_response)
1400
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1401
+ end
1402
+ end
1403
+
1404
+ context 'without integrity negotiate context' do
1405
+ it 'raises the expected exception' do
1406
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1407
+ smb3_response.add_negotiate_context(nc_encryption)
1408
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1409
+ RubySMB::Error::EncryptionError,
1410
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1411
+ )
1412
+ end
1413
+ end
1414
+
1415
+ context 'with an unknown integrity hash algorithm' do
1416
+ it 'raises the expected exception' do
1417
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1418
+ smb3_response.add_negotiate_context(nc_encryption)
1419
+ nc = RubySMB::SMB2::NegotiateContext.new(
1420
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1421
+ )
1422
+ nc.data.hash_algorithms << 5
1423
+ smb3_response.add_negotiate_context(nc)
1424
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1425
+ RubySMB::Error::EncryptionError,
1426
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1427
+ )
1428
+ end
1429
+ end
1430
+ end
1431
+
1432
+ context 'when selecting the encryption algorithm' do
1433
+ context 'with one algorithm' do
1434
+ it 'selects the expected algorithm' do
1435
+ smb3_client.parse_smb3_capabilities(smb3_response)
1436
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1437
+ end
1438
+ end
1439
+
1440
+ context 'with multiple algorithms' do
1441
+ it 'selects the AES-128-GCM algorithm if included' do
1442
+ nc = smb3_response.find_negotiate_context(
1443
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1444
+ )
1445
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1446
+ smb3_client.parse_smb3_capabilities(smb3_response)
1447
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
1448
+ end
1449
+
1450
+ it 'selects the first algorithm if AES-128-GCM is not included' do
1451
+ nc = smb3_response.find_negotiate_context(
1452
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1453
+ )
1454
+ nc.data.ciphers << 3
1455
+ smb3_client.parse_smb3_capabilities(smb3_response)
1456
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1457
+ end
1458
+
1459
+ it 'keep tracks of the server supported algorithms' do
1460
+ nc = smb3_response.find_negotiate_context(
1461
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1462
+ )
1463
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1464
+ smb3_client.parse_smb3_capabilities(smb3_response)
1465
+ expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
1466
+ end
1467
+ end
1468
+
1469
+ context 'without encryption context' do
1470
+ it 'raises the expected exception' do
1471
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1472
+ smb3_response.add_negotiate_context(nc_integrity)
1473
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1474
+ RubySMB::Error::EncryptionError,
1475
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1476
+ )
1477
+ end
1478
+ end
1479
+
1480
+ context 'with an unknown encryption algorithm' do
1481
+ it 'raises the expected exception' do
1482
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1483
+ smb3_response.add_negotiate_context(nc_integrity)
1484
+ nc = RubySMB::SMB2::NegotiateContext.new(
1485
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1486
+ )
1487
+ nc.data.ciphers << 14
1488
+ smb3_response.add_negotiate_context(nc)
1489
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1490
+ RubySMB::Error::EncryptionError,
1491
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1492
+ )
1493
+ end
1494
+ end
1495
+ end
1496
+
1497
+ context 'when selecting the compression algorithm' do
1498
+ it 'keep tracks of the server supported algorithms' do
1499
+ nc = RubySMB::SMB2::NegotiateContext.new(
1500
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
1501
+ )
1502
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
1503
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
1504
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
1505
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
1506
+ smb3_response.add_negotiate_context(nc)
1507
+ smb3_client.parse_smb3_capabilities(smb3_response)
1508
+ expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
1509
+ end
1510
+ end
552
1511
  end
553
1512
  end
554
1513
  end
@@ -875,6 +1834,39 @@ RSpec.describe RubySMB::Client do
875
1834
  smb2_client.smb2_authenticate
876
1835
  expect(smb2_client.os_version).to eq '6.1.7601'
877
1836
  end
1837
+
1838
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1839
+ it "does not update the preauth hash with dialect #{dialect}" do
1840
+ smb2_client.dialect = dialect
1841
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1842
+ smb2_client.smb2_authenticate
1843
+ end
1844
+ end
1845
+
1846
+ it "updates the preauth hash with dialect 0x0311" do
1847
+ smb2_client.dialect = '0x0311'
1848
+ expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
1849
+ smb2_client.smb2_authenticate
1850
+ end
1851
+
1852
+ context 'when setting the session_encrypt_data parameter' do
1853
+ before :example do
1854
+ smb2_client.smb3 = true
1855
+ smb2_client.session_encrypt_data = false
1856
+ end
1857
+
1858
+ it 'sets the session_encrypt_data parameter to true if the server requires encryption' do
1859
+ final_response_packet.session_flags.encrypt_data = 1
1860
+ smb2_client.smb2_authenticate
1861
+ expect(smb2_client.session_encrypt_data).to be true
1862
+ end
1863
+
1864
+ it 'does not set the session_encrypt_data parameter if the server does not require encryption' do
1865
+ final_response_packet.session_flags.encrypt_data = 0
1866
+ smb2_client.smb2_authenticate
1867
+ expect(smb2_client.session_encrypt_data).to be false
1868
+ end
1869
+ end
878
1870
  end
879
1871
 
880
1872
  describe '#smb2_ntlmssp_negotiate_packet' do
@@ -890,20 +1882,34 @@ RSpec.describe RubySMB::Client do
890
1882
  smb2_client.smb2_ntlmssp_negotiate_packet
891
1883
  end
892
1884
 
893
- it 'sets the message ID in the packet header to 1' do
894
- expect(smb2_client.smb2_ntlmssp_negotiate_packet.smb2_header.message_id).to eq 1
895
- end
896
-
897
- it 'increments client#smb2_message_id' do
898
- expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
1885
+ it 'enables signing' do
1886
+ expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
899
1887
  end
900
1888
  end
901
1889
 
902
1890
  describe '#smb2_ntlmssp_negotiate' do
1891
+ before :example do
1892
+ allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
1893
+ allow(smb2_client).to receive(:send_recv)
1894
+ end
1895
+
903
1896
  it 'sends the request packet and receives a response' do
904
- expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
905
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
906
- expect(dispatcher).to receive(:recv_packet)
1897
+ expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
1898
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1899
+ smb2_client.smb2_ntlmssp_negotiate
1900
+ end
1901
+
1902
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1903
+ it "does not update the preauth hash with dialect #{dialect}" do
1904
+ smb2_client.dialect = dialect
1905
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1906
+ smb2_client.smb2_ntlmssp_negotiate
1907
+ end
1908
+ end
1909
+
1910
+ it "updates the preauth hash with dialect 0x0311" do
1911
+ smb2_client.dialect = '0x0311'
1912
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
907
1913
  smb2_client.smb2_ntlmssp_negotiate
908
1914
  end
909
1915
  end
@@ -961,13 +1967,35 @@ RSpec.describe RubySMB::Client do
961
1967
  it 'sets the session ID on the request packet' do
962
1968
  expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
963
1969
  end
1970
+
1971
+ it 'enables signing' do
1972
+ expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
1973
+ end
964
1974
  end
965
1975
 
966
1976
  describe '#smb2_ntlmssp_authenticate' do
1977
+ before :example do
1978
+ allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
1979
+ allow(smb2_client).to receive(:send_recv)
1980
+ end
1981
+
967
1982
  it 'sends the request packet and receives a response' do
968
- expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
969
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
970
- expect(dispatcher).to receive(:recv_packet)
1983
+ expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
1984
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1985
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1986
+ end
1987
+
1988
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1989
+ it "does not update the preauth hash with dialect #{dialect}" do
1990
+ smb2_client.dialect = dialect
1991
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1992
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1993
+ end
1994
+ end
1995
+
1996
+ it "updates the preauth hash with dialect 0x0311" do
1997
+ smb2_client.dialect = '0x0311'
1998
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
971
1999
  smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
972
2000
  end
973
2001
  end
@@ -1103,6 +2131,108 @@ RSpec.describe RubySMB::Client do
1103
2131
  end
1104
2132
  end
1105
2133
  end
2134
+
2135
+ describe '#smb3_sign' do
2136
+ context 'if signing is required and we have a session key' do
2137
+ let(:request) {
2138
+ packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
2139
+ packet.smb2_header.flags.signed = 1
2140
+ packet.smb2_header.signature = "\x00" * 16
2141
+ packet
2142
+ }
2143
+ let(:session_key) { 'Session Key' }
2144
+ before :example do
2145
+ smb3_client.session_key = session_key
2146
+ smb3_client.signing_required = true
2147
+ end
2148
+
2149
+ ['0x0300', '0x0302'].each do |dialect|
2150
+ context "with #{dialect} dialect" do
2151
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2152
+ smb3_client.dialect = dialect
2153
+ fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
2154
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2155
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2156
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2157
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2158
+ end
2159
+ end
2160
+ end
2161
+
2162
+ context "with 0x0311 dialect" do
2163
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2164
+ smb3_client.dialect = '0x0311'
2165
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2166
+ fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
2167
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2168
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2169
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2170
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2171
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2172
+ end
2173
+ end
2174
+
2175
+ context 'with an incompatible dialect' do
2176
+ it 'raises the expected exception' do
2177
+ smb3_client.dialect = '0x0202'
2178
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2179
+ RubySMB::Error::SigningError,
2180
+ 'Dialect is incompatible with SMBv3 signing'
2181
+ )
2182
+ end
2183
+ end
2184
+ end
2185
+
2186
+ context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
2187
+ let(:request) {
2188
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
2189
+ packet.smb2_header.flags.signed = 1
2190
+ packet.smb2_header.signature = "\x00" * 16
2191
+ packet
2192
+ }
2193
+ let(:session_key) { 'Session Key' }
2194
+ before :example do
2195
+ smb3_client.session_key = session_key
2196
+ smb3_client.signing_required = false
2197
+ end
2198
+
2199
+ ['0x0300', '0x0302'].each do |dialect|
2200
+ context "with #{dialect} dialect" do
2201
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2202
+ smb3_client.dialect = dialect
2203
+ fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
2204
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2205
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2206
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2207
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2208
+ end
2209
+ end
2210
+ end
2211
+
2212
+ context "with 0x0311 dialect" do
2213
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2214
+ smb3_client.dialect = '0x0311'
2215
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2216
+ fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
2217
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2218
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2219
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2220
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2221
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2222
+ end
2223
+ end
2224
+
2225
+ context 'with an incompatible dialect' do
2226
+ it 'raises the expected exception' do
2227
+ smb3_client.dialect = '0x0202'
2228
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2229
+ RubySMB::Error::SigningError,
2230
+ 'Dialect is incompatible with SMBv3 signing'
2231
+ )
2232
+ end
2233
+ end
2234
+ end
2235
+ end
1106
2236
  end
1107
2237
 
1108
2238
  context '#increment_smb_message_id' do
@@ -1156,7 +2286,10 @@ RSpec.describe RubySMB::Client do
1156
2286
 
1157
2287
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1158
2288
  response.smb_header.nt_status = 0xc0000015
1159
- expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
2289
+ expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
2290
+ RubySMB::Error::UnexpectedStatusCode,
2291
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2292
+ )
1160
2293
  end
1161
2294
 
1162
2295
  it 'creates a new Tree from itself, the share path, and the response packet' do
@@ -1177,11 +2310,14 @@ RSpec.describe RubySMB::Client do
1177
2310
  }
1178
2311
 
1179
2312
  describe '#smb2_tree_connect' do
1180
- it 'builds and sends a TreeconnectRequest for the supplied share' do
2313
+ it 'builds and sends the expected TreeconnectRequest for the supplied share' do
1181
2314
  allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
1182
- modified_request = request
1183
- modified_request.encode_path(path)
1184
- expect(smb2_client).to receive(:send_recv).with(modified_request).and_return(response.to_binary_s)
2315
+ expect(smb2_client).to receive(:send_recv) do |req|
2316
+ expect(req).to eq(request)
2317
+ expect(req.smb2_header.tree_id).to eq(65_535)
2318
+ expect(req.path).to eq(path.encode('UTF-16LE'))
2319
+ response.to_binary_s
2320
+ end
1185
2321
  smb2_client.smb2_tree_connect(path)
1186
2322
  end
1187
2323
 
@@ -1200,11 +2336,20 @@ RSpec.describe RubySMB::Client do
1200
2336
 
1201
2337
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1202
2338
  response.smb2_header.nt_status = 0xc0000015
1203
- expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
2339
+ expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
2340
+ RubySMB::Error::UnexpectedStatusCode,
2341
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2342
+ )
1204
2343
  end
1205
2344
 
1206
2345
  it 'creates a new Tree from itself, the share path, and the response packet' do
1207
- expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
2346
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
2347
+ smb2_client.smb2_tree_from_response(path, response)
2348
+ end
2349
+
2350
+ it 'creates a new with encryption set if the response requires it' do
2351
+ response.share_flags.encrypt = 1
2352
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
1208
2353
  smb2_client.smb2_tree_from_response(path, response)
1209
2354
  end
1210
2355
  end
@@ -1214,7 +2359,7 @@ RSpec.describe RubySMB::Client do
1214
2359
  let(:named_pipe){ double("Named Pipe") }
1215
2360
 
1216
2361
  before :example do
1217
- allow(tree).to receive(:open_file).and_return(named_pipe)
2362
+ allow(tree).to receive(:open_pipe).and_return(named_pipe)
1218
2363
  allow(named_pipe).to receive(:net_share_enum_all)
1219
2364
  end
1220
2365
 
@@ -1229,8 +2374,8 @@ RSpec.describe RubySMB::Client do
1229
2374
  smb1_client.net_share_enum_all(sock.peeraddr)
1230
2375
  end
1231
2376
 
1232
- it 'it calls the Tree #open_file method to open "srvsvc" named pipe' do
1233
- expect(tree).to receive(:open_file).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
2377
+ it 'it calls the Tree #open_pipe method to open "srvsvc" named pipe' do
2378
+ expect(tree).to receive(:open_pipe).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
1234
2379
  smb1_client.net_share_enum_all(sock.peeraddr)
1235
2380
  end
1236
2381
 
@@ -1252,8 +2397,8 @@ RSpec.describe RubySMB::Client do
1252
2397
  smb2_client.net_share_enum_all(sock.peeraddr)
1253
2398
  end
1254
2399
 
1255
- it 'it calls the Tree #open_file method to open "srvsvc" named pipe' do
1256
- expect(tree).to receive(:open_file).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
2400
+ it 'it calls the Tree #open_pipe method to open "srvsvc" named pipe' do
2401
+ expect(tree).to receive(:open_pipe).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
1257
2402
  smb2_client.net_share_enum_all(sock.peeraddr)
1258
2403
  end
1259
2404
 
@@ -1301,7 +2446,7 @@ RSpec.describe RubySMB::Client do
1301
2446
  end
1302
2447
 
1303
2448
  it 'raise an InvalidPacket exception when the response is not valid' do
1304
- echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
2449
+ echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
1305
2450
  allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
1306
2451
  expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
1307
2452
  end
@@ -1334,7 +2479,7 @@ RSpec.describe RubySMB::Client do
1334
2479
  before :example do
1335
2480
  allow(ipc_tree).to receive_messages(
1336
2481
  :share => share,
1337
- :open_file => named_pipe
2482
+ :open_pipe => named_pipe
1338
2483
  )
1339
2484
  allow(client).to receive(:tree_connect).and_return(ipc_tree)
1340
2485
  end
@@ -1356,9 +2501,9 @@ RSpec.describe RubySMB::Client do
1356
2501
  expect(client).to have_received(:tree_connect).with(share)
1357
2502
  end
1358
2503
 
1359
- it 'open \'winreg\' file on the IPC$ Tree' do
2504
+ it 'open \'winreg\' pipe on the IPC$ Tree' do
1360
2505
  client.connect_to_winreg(host)
1361
- expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
2506
+ expect(ipc_tree).to have_received(:open_pipe).with(filename: "winreg", write: true, read: true)
1362
2507
  end
1363
2508
 
1364
2509
  it 'returns the expected opened named pipe' do
@@ -1473,5 +2618,198 @@ RSpec.describe RubySMB::Client do
1473
2618
  end
1474
2619
  end
1475
2620
  end
2621
+
2622
+ describe '#update_preauth_hash' do
2623
+ it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
2624
+ expect { client.update_preauth_hash('Test') }.to raise_error(
2625
+ RubySMB::Error::EncryptionError,
2626
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
2627
+ )
2628
+ end
2629
+
2630
+ it 'computes the hash value' do
2631
+ packet = RubySMB::SMB2::Packet::EchoRequest.new
2632
+ data = 'Previous hash'
2633
+ algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
2634
+ RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
2635
+ ]
2636
+ client.preauth_integrity_hash_algorithm = algo
2637
+ client.preauth_integrity_hash_value = data
2638
+ hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
2639
+ client.update_preauth_hash(packet)
2640
+ expect(client.preauth_integrity_hash_value).to eq(hash)
2641
+ end
2642
+ end
2643
+
2644
+ context 'Encryption' do
2645
+ describe '#smb3_encrypt' do
2646
+ let(:transform_packet) { double('TransformHeader packet') }
2647
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2648
+ let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
2649
+
2650
+ before :example do
2651
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
2652
+ allow(transform_packet).to receive(:encrypt)
2653
+ client.session_key = session_key
2654
+ end
2655
+
2656
+ it 'does not generate a new client encryption key if it already exists' do
2657
+ client.client_encryption_key = 'key'
2658
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2659
+ expect(client.client_encryption_key).to eq('key')
2660
+ client.smb3_encrypt(data)
2661
+ end
2662
+
2663
+ ['0x0300', '0x0302'].each do |dialect|
2664
+ context "with #{dialect} dialect" do
2665
+ before :example do
2666
+ client.dialect = dialect
2667
+ end
2668
+
2669
+ it 'generates the client encryption key with the expected parameters' do
2670
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2671
+ session_key,
2672
+ "SMB2AESCCM\x00",
2673
+ "ServerIn \x00"
2674
+ ).and_call_original
2675
+ client.smb3_encrypt(data)
2676
+ end
2677
+ end
2678
+ end
2679
+
2680
+ context 'with 0x0311 dialect' do
2681
+ it 'generates the client encryption key with the expected parameters' do
2682
+ client.preauth_integrity_hash_value = ''
2683
+ client.dialect = '0x0311'
2684
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2685
+ session_key,
2686
+ "SMBC2SCipherKey\x00",
2687
+ ''
2688
+ ).and_call_original
2689
+ client.smb3_encrypt(data)
2690
+ end
2691
+ end
2692
+
2693
+ it 'raises the expected exception if the dialect is incompatible' do
2694
+ client.dialect = '0x0202'
2695
+ expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
2696
+ end
2697
+
2698
+ it 'creates a TransformHeader packet and encrypt the data' do
2699
+ client.dialect = '0x0300'
2700
+ client.encryption_algorithm = 'AES-128-CCM'
2701
+ client.session_id = 123
2702
+ client.smb3_encrypt(data)
2703
+ expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
2704
+ expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
2705
+ end
2706
+
2707
+ it 'generates the expected client encryption key with 0x0302 dialect' do
2708
+ client.dialect = '0x0302'
2709
+ expected_enc_key =
2710
+ "\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
2711
+ client.smb3_encrypt(data)
2712
+ expect(client.client_encryption_key).to eq expected_enc_key
2713
+ end
2714
+
2715
+ it 'generates the expected client encryption key with 0x0311 dialect' do
2716
+ client.dialect = '0x0311'
2717
+ client.session_key =
2718
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2719
+ client.preauth_integrity_hash_value =
2720
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2721
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2722
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2723
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2724
+ expected_enc_key =
2725
+ "\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
2726
+ client.smb3_encrypt(data)
2727
+ expect(client.client_encryption_key).to eq expected_enc_key
2728
+ end
2729
+ end
2730
+
2731
+ describe '#smb3_decrypt' do
2732
+ let(:transform_packet) { double('TransformHeader packet') }
2733
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2734
+
2735
+ before :example do
2736
+ allow(transform_packet).to receive(:decrypt)
2737
+ client.session_key = session_key
2738
+ end
2739
+
2740
+ it 'does not generate a new server encryption key if it already exists' do
2741
+ client.server_encryption_key = 'key'
2742
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2743
+ expect(client.server_encryption_key).to eq('key')
2744
+ client.smb3_decrypt(transform_packet)
2745
+ end
2746
+
2747
+ ['0x0300', '0x0302'].each do |dialect|
2748
+ context "with #{dialect} dialect" do
2749
+ before :example do
2750
+ client.dialect = dialect
2751
+ end
2752
+
2753
+ it 'generates the client encryption key with the expected parameters' do
2754
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2755
+ session_key,
2756
+ "SMB2AESCCM\x00",
2757
+ "ServerOut\x00"
2758
+ ).and_call_original
2759
+ client.smb3_decrypt(transform_packet)
2760
+ end
2761
+ end
2762
+ end
2763
+
2764
+ context 'with 0x0311 dialect' do
2765
+ it 'generates the client encryption key with the expected parameters' do
2766
+ client.preauth_integrity_hash_value = ''
2767
+ client.dialect = '0x0311'
2768
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2769
+ session_key,
2770
+ "SMBS2CCipherKey\x00",
2771
+ ''
2772
+ ).and_call_original
2773
+ client.smb3_decrypt(transform_packet)
2774
+ end
2775
+ end
2776
+
2777
+ it 'raises the expected exception if the dialect is incompatible' do
2778
+ client.dialect = '0x0202'
2779
+ expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
2780
+ end
2781
+
2782
+ it 'creates a TransformHeader packet and encrypt the data' do
2783
+ client.dialect = '0x0300'
2784
+ client.encryption_algorithm = 'AES-128-CCM'
2785
+ client.session_id = 123
2786
+ client.smb3_decrypt(transform_packet)
2787
+ expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
2788
+ end
2789
+
2790
+ it 'generates the expected server encryption key with 0x0302 dialect' do
2791
+ client.dialect = '0x0302'
2792
+ expected_enc_key =
2793
+ "\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
2794
+ client.smb3_decrypt(transform_packet)
2795
+ expect(client.server_encryption_key).to eq expected_enc_key
2796
+ end
2797
+
2798
+ it 'generates the expected server encryption key with 0x0311 dialect' do
2799
+ client.dialect = '0x0311'
2800
+ client.session_key =
2801
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2802
+ client.preauth_integrity_hash_value =
2803
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2804
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2805
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2806
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2807
+ expected_enc_key =
2808
+ "\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
2809
+ client.smb3_decrypt(transform_packet)
2810
+ expect(client.server_encryption_key).to eq expected_enc_key
2811
+ end
2812
+ end
2813
+ end
1476
2814
  end
1477
2815