ruby_smb 1.0.5 → 2.0.3

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 (191) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/anonymous_auth.rb +3 -3
  8. data/examples/append_file.rb +10 -8
  9. data/examples/authenticate.rb +9 -5
  10. data/examples/delete_file.rb +8 -6
  11. data/examples/enum_registry_key.rb +29 -0
  12. data/examples/enum_registry_values.rb +31 -0
  13. data/examples/list_directory.rb +8 -6
  14. data/examples/negotiate.rb +51 -8
  15. data/examples/negotiate_with_netbios_service.rb +9 -5
  16. data/examples/net_share_enum_all.rb +6 -4
  17. data/examples/pipes.rb +13 -13
  18. data/examples/query_service_status.rb +64 -0
  19. data/examples/read_file.rb +8 -6
  20. data/examples/read_file_encryption.rb +56 -0
  21. data/examples/read_registry_key_value.rb +33 -0
  22. data/examples/rename_file.rb +9 -7
  23. data/examples/tree_connect.rb +7 -5
  24. data/examples/write_file.rb +9 -7
  25. data/lib/ruby_smb.rb +4 -1
  26. data/lib/ruby_smb/client.rb +239 -21
  27. data/lib/ruby_smb/client/authentication.rb +27 -8
  28. data/lib/ruby_smb/client/encryption.rb +62 -0
  29. data/lib/ruby_smb/client/negotiation.rb +154 -12
  30. data/lib/ruby_smb/client/signing.rb +19 -0
  31. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  32. data/lib/ruby_smb/client/utils.rb +8 -7
  33. data/lib/ruby_smb/client/winreg.rb +46 -0
  34. data/lib/ruby_smb/crypto.rb +30 -0
  35. data/lib/ruby_smb/dcerpc.rb +40 -0
  36. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  37. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  38. data/lib/ruby_smb/dcerpc/error.rb +6 -0
  39. data/lib/ruby_smb/dcerpc/ndr.rb +260 -16
  40. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  41. data/lib/ruby_smb/dcerpc/request.rb +41 -9
  42. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
  43. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +38 -0
  44. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  45. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  46. data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
  47. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
  48. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
  49. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
  50. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
  51. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
  52. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
  53. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
  54. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
  55. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
  56. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
  57. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
  58. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
  59. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
  60. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
  61. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
  62. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
  63. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
  64. data/lib/ruby_smb/dcerpc/winreg.rb +421 -0
  65. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  66. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  67. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
  68. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
  69. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  70. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  71. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  72. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  73. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  74. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  75. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  76. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  77. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  78. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  79. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +40 -0
  80. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  81. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  82. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
  83. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
  84. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  85. data/lib/ruby_smb/dispatcher/socket.rb +5 -4
  86. data/lib/ruby_smb/error.rb +28 -1
  87. data/lib/ruby_smb/field/stringz16.rb +17 -1
  88. data/lib/ruby_smb/nbss/session_header.rb +4 -4
  89. data/lib/ruby_smb/smb1/commands.rb +1 -1
  90. data/lib/ruby_smb/smb1/file.rb +8 -14
  91. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  92. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  93. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  94. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  95. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  96. data/lib/ruby_smb/smb1/pipe.rb +81 -3
  97. data/lib/ruby_smb/smb1/tree.rb +12 -3
  98. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  99. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  100. data/lib/ruby_smb/smb2/file.rb +51 -61
  101. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  102. data/lib/ruby_smb/smb2/packet.rb +2 -0
  103. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  104. data/lib/ruby_smb/smb2/packet/error_packet.rb +2 -4
  105. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  106. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  107. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  108. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  109. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  110. data/lib/ruby_smb/smb2/pipe.rb +80 -3
  111. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  112. data/lib/ruby_smb/smb2/tree.rb +32 -20
  113. data/lib/ruby_smb/version.rb +1 -1
  114. data/ruby_smb.gemspec +5 -3
  115. data/spec/lib/ruby_smb/client_spec.rb +1583 -102
  116. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  117. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  118. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  119. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1729 -0
  120. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  121. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
  122. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +135 -0
  123. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  124. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  125. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
  126. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
  127. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
  128. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
  129. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
  130. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
  131. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
  132. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
  133. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
  134. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
  135. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
  136. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
  137. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
  138. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
  139. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
  140. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
  141. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
  142. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
  143. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  144. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  145. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
  146. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
  147. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +104 -0
  148. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  149. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  150. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  151. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  152. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  153. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +95 -0
  154. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  155. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +35 -0
  156. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  157. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  158. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +138 -0
  159. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  160. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
  161. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
  162. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +884 -0
  163. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  164. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +12 -12
  165. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  166. data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
  167. data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
  168. data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
  169. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  170. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  171. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  172. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  173. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +216 -147
  174. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  175. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  176. data/spec/lib/ruby_smb/smb2/file_spec.rb +146 -68
  177. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  178. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  179. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +3 -24
  180. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  181. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  182. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  183. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  184. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  185. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +226 -148
  186. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  187. data/spec/lib/ruby_smb/smb2/tree_spec.rb +88 -9
  188. metadata +257 -81
  189. metadata.gz.sig +0 -0
  190. data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
  191. data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -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
@@ -3,13 +3,26 @@ module RubySMB
3
3
  # Represents a pipe on the Remote server that we can perform
4
4
  # various I/O operations on.
5
5
  class Pipe < File
6
- require 'ruby_smb/smb2/dcerpc'
6
+ require 'ruby_smb/dcerpc'
7
7
 
8
- include RubySMB::SMB2::Dcerpc
8
+ include RubySMB::Dcerpc
9
9
 
10
10
  STATUS_CONNECTED = 0x00000003
11
11
  STATUS_CLOSING = 0x00000004
12
12
 
13
+ def initialize(tree:, response:, name:)
14
+ raise ArgumentError, 'No Name Provided' if name.nil?
15
+ case name
16
+ when 'srvsvc'
17
+ extend RubySMB::Dcerpc::Srvsvc
18
+ when 'winreg'
19
+ extend RubySMB::Dcerpc::Winreg
20
+ when 'svcctl'
21
+ extend RubySMB::Dcerpc::Svcctl
22
+ end
23
+ super(tree: tree, response: response, name: name)
24
+ end
25
+
13
26
  # Performs a peek operation on the named pipe
14
27
  #
15
28
  # @param peek_size [Integer] Amount of data to peek
@@ -35,7 +48,7 @@ module RubySMB
35
48
  end
36
49
 
37
50
  unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
38
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
51
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
39
52
  end
40
53
  response
41
54
  end
@@ -67,6 +80,70 @@ module RubySMB
67
80
  state == STATUS_CONNECTED
68
81
  end
69
82
 
83
+ def dcerpc_request(stub_packet, options={})
84
+ options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
85
+ dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
86
+ dcerpc_request.stub.read(stub_packet.to_binary_s)
87
+ ioctl_send_recv(dcerpc_request, options)
88
+ end
89
+
90
+ def ioctl_send_recv(action, options={})
91
+ request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
92
+ request.ctl_code = 0x0011C017
93
+ request.flags.is_fsctl = 0x00000001
94
+ # TODO: handle fragmentation when the request size > MAX_XMIT_FRAG
95
+ request.buffer = action.to_binary_s
96
+
97
+ ioctl_raw_response = @tree.client.send_recv(request)
98
+ ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
99
+ unless ioctl_response.valid?
100
+ raise RubySMB::Error::InvalidPacket.new(
101
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
102
+ expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
103
+ received_proto: ioctl_response.smb2_header.protocol,
104
+ received_cmd: ioctl_response.smb2_header.command
105
+ )
106
+ end
107
+ unless [WindowsError::NTStatus::STATUS_SUCCESS,
108
+ WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
109
+ raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
110
+ end
111
+
112
+ raw_data = ioctl_response.output_data
113
+ if ioctl_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
114
+ raw_data << read(bytes: @tree.client.max_buffer_size - ioctl_response.output_count)
115
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
116
+ unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
117
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
118
+ end
119
+ stub_data = dcerpc_response.stub.to_s
120
+
121
+ loop do
122
+ break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
123
+ raw_data = read(bytes: @tree.client.max_buffer_size)
124
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
125
+ stub_data << dcerpc_response.stub.to_s
126
+ end
127
+ stub_data
128
+ else
129
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
130
+ dcerpc_response.stub.to_s
131
+ end
132
+ end
133
+
134
+
135
+ private
136
+
137
+ def dcerpc_response_from_raw_response(raw_data)
138
+ dcerpc_response = RubySMB::Dcerpc::Response.read(raw_data)
139
+ unless dcerpc_response.pdu_header.ptype == RubySMB::Dcerpc::PTypes::RESPONSE
140
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not a Response packet"
141
+ end
142
+ dcerpc_response
143
+ rescue IOError
144
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Error reading the DCERPC response"
145
+ end
146
+
70
147
  end
71
148
  end
72
149
  end
@@ -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,7 +44,7 @@ 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(
@@ -96,7 +102,7 @@ module RubySMB
96
102
  create_request.create_disposition = disposition
97
103
  create_request.name = filename
98
104
 
99
- raw_response = client.send_recv(create_request)
105
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
100
106
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
101
107
  unless response.valid?
102
108
  raise RubySMB::Error::InvalidPacket.new(
@@ -107,15 +113,15 @@ module RubySMB
107
113
  )
108
114
  end
109
115
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
110
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
116
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
111
117
  end
112
118
 
113
119
  case @share_type
114
- when 0x01
115
- RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
116
- when 0x02
120
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
121
+ RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
122
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
117
123
  RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
118
- # when 0x03
124
+ # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
119
125
  # it's a printer!
120
126
  else
121
127
  raise RubySMB::Error::RubySMBError, 'Unsupported share type'
@@ -141,14 +147,21 @@ module RubySMB
141
147
  directory_request.file_information_class = type::CLASS_LEVEL
142
148
  directory_request.file_id = file_id
143
149
  directory_request.name = pattern
144
- directory_request.output_length = 65_535
150
+
151
+ max_read = client.server_max_read_size
152
+ max_read = 65536 unless client.server_supports_multi_credit
153
+ credit_charge = 0
154
+ if client.server_supports_multi_credit
155
+ credit_charge = (max_read - 1) / 65536 + 1
156
+ end
157
+ directory_request.output_length = max_read
158
+ directory_request.smb2_header.credit_charge = credit_charge
145
159
 
146
160
  directory_request = set_header_fields(directory_request)
147
161
 
148
162
  files = []
149
-
150
163
  loop do
151
- response = client.send_recv(directory_request)
164
+ response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
152
165
  directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
153
166
  unless directory_response.valid?
154
167
  raise RubySMB::Error::InvalidPacket.new(
@@ -164,7 +177,7 @@ module RubySMB
164
177
  break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
165
178
 
166
179
  unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
167
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
180
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
168
181
  end
169
182
 
170
183
  files += directory_response.results(type)
@@ -193,7 +206,7 @@ module RubySMB
193
206
 
194
207
  create_request = open_directory_packet(directory: directory, disposition: disposition,
195
208
  impersonation: impersonation, read: read, write: write, delete: delete)
196
- raw_response = client.send_recv(create_request)
209
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
197
210
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
198
211
  unless response.valid?
199
212
  raise RubySMB::Error::InvalidPacket.new(
@@ -204,7 +217,7 @@ module RubySMB
204
217
  )
205
218
  end
206
219
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
207
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
220
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
208
221
  end
209
222
 
210
223
  response
@@ -250,7 +263,6 @@ module RubySMB
250
263
  # @return [RubySMB::SMB2::Packet] the modified packet.
251
264
  def set_header_fields(request)
252
265
  request.smb2_header.tree_id = id
253
- request.smb2_header.credit_charge = 1
254
266
  request.smb2_header.credits = 256
255
267
  request
256
268
  end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '1.0.5'.freeze
2
+ VERSION = '2.0.3'.freeze
3
3
  end
@@ -6,8 +6,8 @@ require 'ruby_smb/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'ruby_smb'
8
8
  spec.version = RubySMB::VERSION
9
- spec.authors = ['David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
10
- spec.email = ['DMaloney@rapid7.com', 'egypt@metasploit.com', 'dev_mohanty@rapid7.com', 'paristvinternet-github@yahoo.com']
9
+ spec.authors = ['Metasploit Hackers', 'David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
10
+ spec.email = ['msfdev@metasploit.com']
11
11
  spec.summary = 'A pure Ruby implementation of the SMB Protocol Family'
12
12
  spec.description = ''
13
13
  spec.homepage = 'https://github.com/rapid7/ruby_smb'
@@ -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.2.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,362 @@ 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 #msb2_message_id with SMB2 header #credit_charge if the dialect is not 0x0202' do
336
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
337
+ smb2_client.smb2_message_id = 0
338
+ smb2_client.dialect = '0x0210'
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 dialect is 0x0202' do
345
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
346
+ smb2_client.smb2_message_id = 0
347
+ smb2_client.dialect = '0x0202'
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
+ end
353
+
354
+ describe '#is_status_pending?' do
355
+ let(:response) {
356
+ res = RubySMB::SMB2::Packet::SessionSetupRequest.new
357
+ res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
358
+ res.smb2_header.flags.async_command = 1
359
+ res
360
+ }
361
+
362
+ it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
363
+ expect(client.is_status_pending?(response.smb2_header)).to be true
364
+ end
365
+
366
+ it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
367
+ response.smb2_header.flags.async_command = 0
368
+ expect(client.is_status_pending?(response.smb2_header)).to be false
369
+ end
370
+
371
+ it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
372
+ response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
373
+ expect(client.is_status_pending?(response.smb2_header)).to be false
374
+ end
375
+ end
376
+
377
+ describe '#can_be_encrypted?' do
378
+ it 'returns true if the packet can be encrypted' do
379
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
380
+ expect(client.can_be_encrypted?(packet)).to be true
381
+ end
382
+
383
+ it 'returns false if it is an SMB1 packet' do
384
+ packet = RubySMB::SMB1::Packet::LogoffRequest.new
385
+ expect(client.can_be_encrypted?(packet)).to be false
386
+ end
387
+
388
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
389
+ it "returns false if the packet is a #{klass}" do
390
+ packet = klass.new
391
+ expect(client.can_be_encrypted?(packet)).to be false
392
+ end
393
+ end
394
+ end
395
+
396
+ describe '#encryption_supported?' do
397
+ ['0x0300', '0x0302', '0x0311'].each do |dialect|
398
+ it "returns true if the dialect is #{dialect}" do
399
+ client.dialect = dialect
400
+ expect(client.encryption_supported?).to be true
401
+ end
402
+ end
403
+
404
+ it "returns false if the dialect does not support encryption" do
405
+ client.dialect = '0x0202'
406
+ expect(client.encryption_supported?).to be false
407
+ end
408
+ end
409
+
410
+ describe '#send_packet' do
411
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
412
+ before :example do
413
+ allow(dispatcher).to receive(:send_packet)
414
+ client.dialect = '0x0300'
415
+ end
416
+
417
+ it 'does not encrypt the packet' do
418
+ expect(client).to_not receive(:smb3_encrypt)
419
+ client.send_packet(packet)
420
+ end
421
+
422
+ it 'sends the packet through the dispatcher' do
423
+ client.send_packet(packet)
424
+ expect(dispatcher).to have_received(:send_packet).with(packet)
425
+ end
426
+
427
+ context 'with encryption' do
428
+ it 'creates a Transform request' do
429
+ expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
430
+ client.send_packet(packet, encrypt: true)
431
+ end
432
+
433
+ it 'raises an EncryptionError exception if an error occurs while encrypting' do
434
+ allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
435
+ expect { client.send_packet(packet, encrypt: true) }.to raise_error(
436
+ RubySMB::Error::EncryptionError,
437
+ "Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
438
+ )
439
+ end
440
+
441
+ it 'sends the encrypted packet' do
442
+ encrypted_packet = double('Encrypted packet')
443
+ allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
444
+ client.send_packet(packet, encrypt: true)
445
+ expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
446
+ end
447
+ end
448
+ end
449
+
450
+ describe '#recv_packet' do
451
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
452
+ before :example do
453
+ allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
454
+ client.dialect = '0x0300'
455
+ allow(client).to receive(:smb3_decrypt)
456
+ end
457
+
458
+ it 'reads the response packet' do
459
+ client.recv_packet
460
+ expect(dispatcher).to have_received(:recv_packet)
461
+ end
462
+
463
+ it 'raises an CommunicationError exception if an error occurs while receiving the response' do
464
+ allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
465
+ expect { client.recv_packet }.to raise_error(RubySMB::Error::CommunicationError)
466
+ end
467
+
468
+ context 'with encryption' do
469
+ it 'raises an EncryptionError exception if an error occurs while receiving the response' do
470
+ allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
471
+ expect { client.recv_packet(encrypt: true) }.to raise_error(
472
+ RubySMB::Error::EncryptionError,
473
+ 'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
474
+ 'The server supports encryption but was not able to handle the encrypted request.'
475
+ )
476
+ end
477
+
478
+ it 'parses the response as a Transform response packet' do
479
+ expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
480
+ client.recv_packet(encrypt: true)
481
+ end
482
+
483
+ it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
484
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
485
+ expect { client.recv_packet(encrypt: true) }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
486
+ end
487
+
488
+ it 'decrypts the Transform response packet' do
489
+ transform = double('Transform header packet')
490
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
491
+ client.recv_packet(encrypt: true)
492
+ expect(client).to have_received(:smb3_decrypt).with(transform)
493
+ end
494
+
495
+ it 'raises an EncryptionError exception if an error occurs while decrypting' do
496
+ allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
497
+ expect { client.recv_packet(encrypt: true) }.to raise_error(
498
+ RubySMB::Error::EncryptionError,
499
+ 'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
500
+ )
501
+ end
101
502
  end
102
503
  end
103
504
 
@@ -240,6 +641,44 @@ RSpec.describe RubySMB::Client do
240
641
  expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
241
642
  end
242
643
  end
644
+
645
+ context 'with SMB3' do
646
+ let(:raw_response) { double('Raw response') }
647
+ let(:logoff_response) {
648
+ RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
649
+ }
650
+ before :example do
651
+ allow(smb3_client).to receive(:send_recv).and_return(raw_response)
652
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
653
+ allow(smb3_client).to receive(:wipe_state!)
654
+ end
655
+
656
+ it 'creates a LogoffRequest packet' do
657
+ expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
658
+ smb3_client.logoff!
659
+ end
660
+
661
+ it 'calls #send_recv' do
662
+ expect(smb3_client).to receive(:send_recv)
663
+ smb3_client.logoff!
664
+ end
665
+
666
+ it 'reads the raw response as a LogoffResponse packet' do
667
+ expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
668
+ smb3_client.logoff!
669
+ end
670
+
671
+ it 'raise an InvalidPacket exception when the response is an error packet' do
672
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
673
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
674
+ end
675
+
676
+ it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
677
+ logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
678
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
679
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
680
+ end
681
+ end
243
682
  end
244
683
 
245
684
  context 'NetBIOS Session Service' do
@@ -310,7 +749,7 @@ RSpec.describe RubySMB::Client do
310
749
  expect(session_packet.session_header.session_packet_type).to eq RubySMB::Nbss::SESSION_REQUEST
311
750
  expect(session_packet.called_name).to eq called_name
312
751
  expect(session_packet.calling_name).to eq calling_name
313
- expect(session_packet.session_header.packet_length).to eq(
752
+ expect(session_packet.session_header.stream_protocol_length).to eq(
314
753
  session_packet.called_name.to_binary_s.size + session_packet.calling_name.to_binary_s.size
315
754
  )
316
755
  end
@@ -365,7 +804,8 @@ RSpec.describe RubySMB::Client do
365
804
  smb1_extended_response.to_binary_s
366
805
  }
367
806
 
368
- let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
807
+ let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
808
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
369
809
 
370
810
  describe '#smb1_negotiate_request' do
371
811
  it 'returns an SMB1 Negotiate Request packet' do
@@ -373,33 +813,158 @@ RSpec.describe RubySMB::Client do
373
813
  end
374
814
 
375
815
  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)
816
+ expect(client.smb1_negotiate_request.dialects).to include(
817
+ buffer_format: 2,
818
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
819
+ )
377
820
  end
378
821
 
379
822
  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)
823
+ expect(client.smb1_negotiate_request.dialects).to include(
824
+ buffer_format: 2,
825
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
826
+ )
381
827
  end
382
828
 
383
829
  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)
830
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
831
+ buffer_format: 2,
832
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
833
+ )
385
834
  end
386
835
 
387
836
  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)
837
+ expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
838
+ buffer_format: 2,
839
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
840
+ )
841
+ end
842
+
843
+ it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
844
+ expect(client.smb1_negotiate_request.dialects).to include(
845
+ buffer_format: 2,
846
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
847
+ )
848
+ end
849
+
850
+ it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
851
+ expect(smb3_client.smb1_negotiate_request.dialects).to include(
852
+ buffer_format: 2,
853
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
854
+ )
855
+ end
856
+
857
+ it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
858
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
859
+ buffer_format: 2,
860
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
861
+ )
389
862
  end
390
863
  end
391
864
 
392
- describe '#smb2_negotiate_request' do
865
+ describe '#smb2_3_negotiate_request' do
393
866
  it 'return an SMB2 Negotiate Request packet' do
394
- expect(client.smb2_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
867
+ expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
395
868
  end
396
869
 
397
- it 'sets the default SMB2 Dialect' do
398
- expect(client.smb2_negotiate_request.dialects).to include(RubySMB::Client::SMB2_DIALECT_DEFAULT)
870
+ it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
871
+ expect(client.smb2_3_negotiate_request.dialects).to include(
872
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
873
+ )
874
+ end
875
+
876
+ it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
877
+ expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
878
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
879
+ )
399
880
  end
400
881
 
401
882
  it 'sets the Message ID to 0' do
402
- expect(client.smb2_negotiate_request.smb2_header.message_id).to eq 0
883
+ expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
884
+ end
885
+
886
+ it 'adds SMB3 dialects if if SMB3 support is enabled' do
887
+ expect(client.smb2_3_negotiate_request.dialects).to include(
888
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
889
+ )
890
+ end
891
+
892
+ it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
893
+ expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
894
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
895
+ )
896
+ end
897
+ end
898
+
899
+ describe '#add_smb3_to_negotiate_request' do
900
+ let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
901
+
902
+ it 'adds the default SMB3 dialects' do
903
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
904
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
905
+ )
906
+ end
907
+
908
+ it 'raises the expected exception when the dialects is not an array of strings' do
909
+ dialects = ['0x0300', 0x0302, '0x0311']
910
+ expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
911
+ end
912
+
913
+ it 'sets encryption capability flag' do
914
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
915
+ end
916
+
917
+ context 'when the negotiate packet includes the 0x0311 dialect' do
918
+ before :example do
919
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
920
+ end
921
+
922
+ it 'adds 3 Negotiate Contexts' do
923
+ expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
924
+ end
925
+
926
+ it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
927
+ nc = negotiate_request.negotiate_context_list.select do |n|
928
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
929
+ end
930
+ expect(nc.length).to eq(1)
931
+ expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
932
+ end
933
+
934
+ it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
935
+ nc = negotiate_request.negotiate_context_list.select do |n|
936
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
937
+ end
938
+ expect(nc.length).to eq(1)
939
+ expect(nc.first.data.ciphers).to eq(
940
+ [
941
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
942
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
943
+ ]
944
+ )
945
+ end
946
+
947
+ it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
948
+ nc = negotiate_request.negotiate_context_list.select do |n|
949
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
950
+ end
951
+ expect(nc.length).to eq(1)
952
+ expect(nc.first.data.compression_algorithms).to eq(
953
+ [
954
+ RubySMB::SMB2::CompressionCapabilities::LZNT1,
955
+ RubySMB::SMB2::CompressionCapabilities::LZ77,
956
+ RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
957
+ RubySMB::SMB2::CompressionCapabilities::Pattern_V1
958
+ ]
959
+ )
960
+ end
961
+ end
962
+
963
+ context 'when the negotiate packet does not include the 0x0311 dialect' do
964
+ it 'does not add any Negotiate Context' do
965
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
966
+ expect(negotiate_request.negotiate_context_list?). to be false
967
+ end
403
968
  end
404
969
  end
405
970
 
@@ -414,10 +979,15 @@ RSpec.describe RubySMB::Client do
414
979
  client.negotiate_request
415
980
  end
416
981
 
417
- it 'calls #smb2_negotiate_request if SMB2 is enabled' do
418
- expect(smb2_client).to receive(:smb2_negotiate_request)
982
+ it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
983
+ expect(smb2_client).to receive(:smb2_3_negotiate_request)
419
984
  smb2_client.negotiate_request
420
985
  end
986
+
987
+ it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
988
+ expect(smb3_client).to receive(:smb2_3_negotiate_request)
989
+ smb3_client.negotiate_request
990
+ end
421
991
  end
422
992
 
423
993
  describe '#negotiate_response' do
@@ -464,12 +1034,28 @@ RSpec.describe RubySMB::Client do
464
1034
  end
465
1035
  end
466
1036
 
467
- context 'with SMB1 and SMB2 enabled' do
1037
+ context 'with only SMB3' do
1038
+ it 'returns a properly formed packet' do
1039
+ expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
1040
+ end
1041
+
1042
+ it 'raises an exception if the Response is invalid' do
1043
+ expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
1044
+ end
1045
+
1046
+ it 'considers the response invalid if it is not an actual Negotiate Response' do
1047
+ bogus_response = smb2_response
1048
+ bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
1049
+ expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
1050
+ end
1051
+ end
1052
+
1053
+ context 'with SMB1, SMB2 and SMB3 enabled' do
468
1054
  it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
469
1055
  expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
470
1056
  end
471
1057
 
472
- it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
1058
+ it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
473
1059
  expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
474
1060
  end
475
1061
  end
@@ -477,78 +1063,446 @@ RSpec.describe RubySMB::Client do
477
1063
 
478
1064
  describe '#parse_negotiate_response' do
479
1065
  context 'when SMB1 was Negotiated' do
480
- it 'turns off SMB2 support' do
1066
+ it 'turns off SMB2 and SMB3 support' do
481
1067
  client.parse_negotiate_response(smb1_extended_response)
482
1068
  expect(client.smb2).to be false
1069
+ expect(client.smb3).to be false
1070
+ end
1071
+
1072
+ it 'sets whether or not signing is required' do
1073
+ smb1_extended_response.parameter_block.security_mode.security_signatures_required = 1
1074
+ client.parse_negotiate_response(smb1_extended_response)
1075
+ expect(client.signing_required).to be true
1076
+ end
1077
+
1078
+ it 'sets #dialect to the negotiated dialect' do
1079
+ smb1_extended_response.dialects = [
1080
+ RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
1081
+ RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
1082
+ RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
1083
+ ]
1084
+ smb1_extended_response.parameter_block.dialect_index = 1
1085
+ client.parse_negotiate_response(smb1_extended_response)
1086
+ expect(client.dialect).to eq 'B'
1087
+ end
1088
+
1089
+ it 'returns the string \'SMB1\'' do
1090
+ expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
1091
+ end
1092
+
1093
+ it 'sets #negotiated_smb_version to 1' do
1094
+ client.parse_negotiate_response(smb1_extended_response)
1095
+ expect(client.negotiated_smb_version).to eq(1)
1096
+ end
1097
+ end
1098
+
1099
+ context 'when SMB2 was negotiated' do
1100
+ it 'turns off SMB1 and SMB3 support' do
1101
+ client.parse_negotiate_response(smb2_response)
1102
+ expect(client.smb1).to be false
1103
+ expect(client.smb3).to be false
1104
+ end
1105
+
1106
+ it 'sets whether or not signing is required' do
1107
+ smb2_response.security_mode.signing_required = 1
1108
+ client.parse_negotiate_response(smb2_response)
1109
+ expect(client.signing_required).to be true
1110
+ end
1111
+
1112
+ it 'sets #dialect to the negotiated dialect' do
1113
+ smb2_response.dialect_revision = 2
1114
+ client.parse_negotiate_response(smb2_response)
1115
+ expect(client.dialect).to eq '0x0002'
1116
+ end
1117
+
1118
+ it 'returns the string \'SMB2\'' do
1119
+ expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
1120
+ end
1121
+
1122
+ it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
1123
+ smb2_response.capabilities.large_mtu = 1
1124
+ client.parse_negotiate_response(smb2_response)
1125
+ expect(client.server_supports_multi_credit).to be true
1126
+ end
1127
+
1128
+ it 'sets #server_supports_multi_credit to false when the dialect is 0x0202' do
1129
+ smb2_response.dialect_revision = 0x0202
1130
+ smb2_response.capabilities.large_mtu = 1 # just to make sure it won't affect the result
1131
+ client.parse_negotiate_response(smb2_response)
1132
+ expect(client.server_supports_multi_credit).to be false
1133
+ end
1134
+ end
1135
+
1136
+ context 'when SMB3 was negotiated' do
1137
+ it 'turns off SMB1 and SMB2 support' do
1138
+ client.parse_negotiate_response(smb3_response)
1139
+ expect(client.smb1).to be false
1140
+ expect(client.smb2).to be false
1141
+ end
1142
+
1143
+ it 'sets whether or not signing is required' do
1144
+ smb3_response.security_mode.signing_required = 1
1145
+ client.parse_negotiate_response(smb3_response)
1146
+ expect(client.signing_required).to be true
1147
+ end
1148
+
1149
+ it 'sets #dialect to the negotiated dialect' do
1150
+ client.parse_negotiate_response(smb3_response)
1151
+ expect(client.dialect).to eq '0x0300'
1152
+ end
1153
+
1154
+ it 'returns the string \'SMB2\'' do
1155
+ expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
1156
+ end
1157
+
1158
+ it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
1159
+ smb3_response.capabilities.large_mtu = 1
1160
+ client.parse_negotiate_response(smb3_response)
1161
+ expect(client.server_supports_multi_credit).to be true
1162
+ end
1163
+
1164
+ context 'when the server supports encryption' do
1165
+ before :example do
1166
+ smb3_response.capabilities.encryption = 1
1167
+ end
1168
+
1169
+ it 'sets the expected encryption algorithm' do
1170
+ client.parse_negotiate_response(smb3_response)
1171
+ expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
1172
+ end
1173
+
1174
+ it 'keeps session encryption enabled if it was already' do
1175
+ client.session_encrypt_data = true
1176
+ client.parse_negotiate_response(smb3_response)
1177
+ expect(client.session_encrypt_data).to be true
1178
+ end
1179
+
1180
+ it 'keeps session encryption disabled if it was already' do
1181
+ client.session_encrypt_data = false
1182
+ client.parse_negotiate_response(smb3_response)
1183
+ expect(client.session_encrypt_data).to be false
1184
+ end
1185
+ end
1186
+
1187
+ context 'when the server does not support encryption' do
1188
+ before :example do
1189
+ smb3_response.capabilities.encryption = 0
1190
+ end
1191
+
1192
+ it 'disables session encryption if it was already enabled' do
1193
+ client.session_encrypt_data = true
1194
+ client.parse_negotiate_response(smb3_response)
1195
+ expect(client.session_encrypt_data).to be false
1196
+ end
1197
+
1198
+ it 'keeps session encryption disabled if it was already' do
1199
+ client.session_encrypt_data = false
1200
+ client.parse_negotiate_response(smb3_response)
1201
+ expect(client.session_encrypt_data).to be false
1202
+ end
1203
+ end
1204
+ end
1205
+
1206
+ context 'when the response contains the SMB2 wildcard revision number dialect' do
1207
+ it 'only turns off SMB1 support' do
1208
+ smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
1209
+ client.parse_negotiate_response(smb2_response)
1210
+ expect(client.smb1).to be false
1211
+ expect(client.smb2).to be true
1212
+ expect(client.smb3).to be true
1213
+ end
1214
+ end
1215
+
1216
+ context 'when the negotiation failed' do
1217
+ context 'with a STATUS_NOT_SUPPORTED status code' do
1218
+ before :example do
1219
+ error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
1220
+ end
1221
+
1222
+ it 'raises the expected exception with SMB2' do
1223
+ expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
1224
+ RubySMB::Error::NegotiationFailure,
1225
+ 'Unable to negotiate with remote host, SMB2 not supported'
1226
+ )
1227
+ end
1228
+
1229
+ it 'raises the expected exception with SMB3' do
1230
+ expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
1231
+ RubySMB::Error::NegotiationFailure,
1232
+ 'Unable to negotiate with remote host, SMB3 not supported'
1233
+ )
1234
+ end
1235
+ end
1236
+
1237
+ context 'with an unknown status code' do
1238
+ it 'raises the expected exception' do
1239
+ expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
1240
+ RubySMB::Error::NegotiationFailure,
1241
+ 'Unable to negotiate with remote host'
1242
+ )
1243
+ end
1244
+ end
1245
+ end
1246
+ end
1247
+
1248
+ describe '#negotiate' do
1249
+ let(:request_packet) { client.smb1_negotiate_request }
1250
+ before :example do
1251
+ allow(client).to receive(:negotiate_request)
1252
+ allow(client).to receive(:send_recv)
1253
+ allow(client).to receive(:negotiate_response)
1254
+ allow(client).to receive(:parse_negotiate_response)
1255
+ end
1256
+
1257
+ it 'calls the backing methods' do
1258
+ expect(client).to receive(:negotiate_request)
1259
+ expect(client).to receive(:send_recv)
1260
+ expect(client).to receive(:negotiate_response)
1261
+ expect(client).to receive(:parse_negotiate_response)
1262
+ client.negotiate
1263
+ end
1264
+
1265
+ context 'with SMB1' do
1266
+ it 'sets the response-packet #dialects array with the dialects sent in the request' do
1267
+ request_packet = client.smb1_negotiate_request
1268
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1269
+ allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
1270
+ expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
1271
+ client.negotiate
1272
+ end
1273
+ end
1274
+
1275
+ context "with 0x0311 dialect" do
1276
+ it 'calls #parse_negotiate_response and updates the preauth hash' do
1277
+ client.dialect = '0x0311'
1278
+ request_packet = client.smb2_3_negotiate_request
1279
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1280
+ allow(client).to receive(:negotiate_response).and_return(smb3_response)
1281
+ expect(client).to receive(:parse_negotiate_response).with(smb3_response)
1282
+ expect(client).to receive(:update_preauth_hash).with(request_packet)
1283
+ expect(client).to receive(:update_preauth_hash).with(smb3_response)
1284
+ client.negotiate
1285
+ end
1286
+ end
1287
+
1288
+ context 'with a wildcard revision number response' do
1289
+ before :example do
1290
+ client.dialect = '0x02ff'
1291
+ allow(client).to receive(:smb2_message_id=) do
1292
+ client.dialect = '0x0202'
1293
+ end
1294
+ end
1295
+
1296
+ it 'increments the message ID' do
1297
+ expect(client).to receive(:smb2_message_id=).with(1)
1298
+ client.negotiate
1299
+ end
1300
+
1301
+ it 're-negotiates' do
1302
+ expect(client).to receive(:negotiate_request).twice
1303
+ expect(client).to receive(:send_recv).twice
1304
+ expect(client).to receive(:negotiate_response).twice
1305
+ expect(client).to receive(:parse_negotiate_response).twice
1306
+ client.negotiate
1307
+ end
1308
+ end
1309
+
1310
+ context 'when an error occurs' do
1311
+ before :example do
1312
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1313
+ allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
1314
+ client.smb1 = false
1315
+ client.smb2 = false
1316
+ client.smb3 = false
483
1317
  end
484
1318
 
485
- it 'sets whether or not signing is required' do
486
- smb1_extended_response.parameter_block.security_mode.security_signatures_required = 1
487
- client.parse_negotiate_response(smb1_extended_response)
488
- expect(client.signing_required).to be true
1319
+ context 'with SMB1' do
1320
+ let(:request_packet) { client.smb1_negotiate_request }
1321
+
1322
+ it 'raise the expected exception' do
1323
+ client.smb1 = true
1324
+ expect { client.negotiate }.to raise_error(
1325
+ RubySMB::Error::NegotiationFailure,
1326
+ "Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
1327
+ )
1328
+ end
489
1329
  end
490
1330
 
491
- it 'sets #dialect to the negotiated dialect' do
492
- smb1_extended_response.dialects = [
493
- RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
494
- RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
495
- RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
496
- ]
497
- smb1_extended_response.parameter_block.dialect_index = 1
498
- client.parse_negotiate_response(smb1_extended_response)
499
- expect(client.dialect).to eq 'B'
1331
+ context 'with SMB2' do
1332
+ let(:request_packet) { client.smb2_3_negotiate_request }
1333
+
1334
+ it 'raise the expected exception' do
1335
+ client.smb2 = true
1336
+ expect { client.negotiate }.to raise_error(
1337
+ RubySMB::Error::NegotiationFailure,
1338
+ "Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
1339
+ )
1340
+ end
500
1341
  end
501
1342
 
502
- it 'returns the string \'SMB1\'' do
503
- expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
1343
+ context 'with SMB3' do
1344
+ let(:request_packet) { client.smb2_3_negotiate_request }
1345
+
1346
+ it 'raise the expected exception' do
1347
+ client.smb3 = true
1348
+ expect { client.negotiate }.to raise_error(
1349
+ RubySMB::Error::NegotiationFailure,
1350
+ "Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
1351
+ )
1352
+ end
504
1353
  end
505
1354
  end
506
1355
 
507
- context 'when SMB2 was negotiated' do
508
- it 'turns off SMB1 support' do
509
- client.parse_negotiate_response(smb2_response)
510
- expect(client.smb1).to be false
1356
+ describe '#parse_smb3_capabilities' do
1357
+ let(:request_packet) { client.smb2_3_negotiate_request }
1358
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
1359
+ let(:nc_encryption) do
1360
+ nc = RubySMB::SMB2::NegotiateContext.new(
1361
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1362
+ )
1363
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
1364
+ nc
511
1365
  end
512
-
513
- it 'sets whether or not signing is required' do
514
- smb2_response.security_mode.signing_required = 1
515
- client.parse_negotiate_response(smb2_response)
516
- expect(client.signing_required).to be true
1366
+ let(:nc_integrity) do
1367
+ nc = RubySMB::SMB2::NegotiateContext.new(
1368
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1369
+ )
1370
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
1371
+ nc
517
1372
  end
518
1373
 
519
- it 'sets #dialect to the negotiated dialect' do
520
- smb2_response.dialect_revision = 2
521
- client.parse_negotiate_response(smb2_response)
522
- expect(client.dialect).to eq '0x0002'
1374
+ before :example do
1375
+ allow(smb3_client).to receive(:update_preauth_hash)
1376
+ smb3_response.add_negotiate_context(nc_encryption)
1377
+ smb3_response.add_negotiate_context(nc_integrity)
523
1378
  end
524
1379
 
525
- it 'returns the string \'SMB2\'' do
526
- expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
1380
+ context 'when selecting the integrity hash algorithm' do
1381
+ context 'with one algorithm' do
1382
+ it 'selects the expected algorithm' do
1383
+ smb3_client.parse_smb3_capabilities(smb3_response)
1384
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1385
+ end
1386
+ end
1387
+
1388
+ context 'with multiple algorithms' do
1389
+ it 'selects the first algorithm' do
1390
+ nc = smb3_response.find_negotiate_context(
1391
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1392
+ )
1393
+ nc.data.hash_algorithms << 3
1394
+ smb3_client.parse_smb3_capabilities(smb3_response)
1395
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1396
+ end
1397
+ end
1398
+
1399
+ context 'without integrity negotiate context' do
1400
+ it 'raises the expected exception' do
1401
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1402
+ smb3_response.add_negotiate_context(nc_encryption)
1403
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1404
+ RubySMB::Error::EncryptionError,
1405
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1406
+ )
1407
+ end
1408
+ end
1409
+
1410
+ context 'with an unknown integrity hash algorithm' do
1411
+ it 'raises the expected exception' do
1412
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1413
+ smb3_response.add_negotiate_context(nc_encryption)
1414
+ nc = RubySMB::SMB2::NegotiateContext.new(
1415
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1416
+ )
1417
+ nc.data.hash_algorithms << 5
1418
+ smb3_response.add_negotiate_context(nc)
1419
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1420
+ RubySMB::Error::EncryptionError,
1421
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1422
+ )
1423
+ end
1424
+ end
527
1425
  end
528
- end
529
- end
530
1426
 
531
- describe '#negotiate' do
532
- it 'calls the backing methods' do
533
- expect(client).to receive(:negotiate_request)
534
- expect(client).to receive(:send_recv)
535
- expect(client).to receive(:negotiate_response)
536
- expect(client).to receive(:parse_negotiate_response)
537
- client.negotiate
538
- end
1427
+ context 'when selecting the encryption algorithm' do
1428
+ context 'with one algorithm' do
1429
+ it 'selects the expected algorithm' do
1430
+ smb3_client.parse_smb3_capabilities(smb3_response)
1431
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1432
+ end
1433
+ end
539
1434
 
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
547
- end
1435
+ context 'with multiple algorithms' do
1436
+ it 'selects the AES-128-GCM algorithm if included' do
1437
+ nc = smb3_response.find_negotiate_context(
1438
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1439
+ )
1440
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1441
+ smb3_client.parse_smb3_capabilities(smb3_response)
1442
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
1443
+ end
1444
+
1445
+ it 'selects the first algorithm if AES-128-GCM is not included' do
1446
+ nc = smb3_response.find_negotiate_context(
1447
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1448
+ )
1449
+ nc.data.ciphers << 3
1450
+ smb3_client.parse_smb3_capabilities(smb3_response)
1451
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1452
+ end
1453
+
1454
+ it 'keep tracks of the server supported algorithms' do
1455
+ nc = smb3_response.find_negotiate_context(
1456
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1457
+ )
1458
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1459
+ smb3_client.parse_smb3_capabilities(smb3_response)
1460
+ expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
1461
+ end
1462
+ end
1463
+
1464
+ context 'without encryption context' do
1465
+ it 'raises the expected exception' do
1466
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1467
+ smb3_response.add_negotiate_context(nc_integrity)
1468
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1469
+ RubySMB::Error::EncryptionError,
1470
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1471
+ )
1472
+ end
1473
+ end
548
1474
 
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)
1475
+ context 'with an unknown encryption algorithm' do
1476
+ it 'raises the expected exception' do
1477
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1478
+ smb3_response.add_negotiate_context(nc_integrity)
1479
+ nc = RubySMB::SMB2::NegotiateContext.new(
1480
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1481
+ )
1482
+ nc.data.ciphers << 14
1483
+ smb3_response.add_negotiate_context(nc)
1484
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1485
+ RubySMB::Error::EncryptionError,
1486
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1487
+ )
1488
+ end
1489
+ end
1490
+ end
1491
+
1492
+ context 'when selecting the compression algorithm' do
1493
+ it 'keep tracks of the server supported algorithms' do
1494
+ nc = RubySMB::SMB2::NegotiateContext.new(
1495
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
1496
+ )
1497
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
1498
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
1499
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
1500
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
1501
+ smb3_response.add_negotiate_context(nc)
1502
+ smb3_client.parse_smb3_capabilities(smb3_response)
1503
+ expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
1504
+ end
1505
+ end
552
1506
  end
553
1507
  end
554
1508
  end
@@ -875,6 +1829,39 @@ RSpec.describe RubySMB::Client do
875
1829
  smb2_client.smb2_authenticate
876
1830
  expect(smb2_client.os_version).to eq '6.1.7601'
877
1831
  end
1832
+
1833
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1834
+ it "does not update the preauth hash with dialect #{dialect}" do
1835
+ smb2_client.dialect = dialect
1836
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1837
+ smb2_client.smb2_authenticate
1838
+ end
1839
+ end
1840
+
1841
+ it "updates the preauth hash with dialect 0x0311" do
1842
+ smb2_client.dialect = '0x0311'
1843
+ expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
1844
+ smb2_client.smb2_authenticate
1845
+ end
1846
+
1847
+ context 'when setting the session_encrypt_data parameter' do
1848
+ before :example do
1849
+ smb2_client.smb3 = true
1850
+ smb2_client.session_encrypt_data = false
1851
+ end
1852
+
1853
+ it 'sets the session_encrypt_data parameter to true if the server requires encryption' do
1854
+ final_response_packet.session_flags.encrypt_data = 1
1855
+ smb2_client.smb2_authenticate
1856
+ expect(smb2_client.session_encrypt_data).to be true
1857
+ end
1858
+
1859
+ it 'does not set the session_encrypt_data parameter if the server does not require encryption' do
1860
+ final_response_packet.session_flags.encrypt_data = 0
1861
+ smb2_client.smb2_authenticate
1862
+ expect(smb2_client.session_encrypt_data).to be false
1863
+ end
1864
+ end
878
1865
  end
879
1866
 
880
1867
  describe '#smb2_ntlmssp_negotiate_packet' do
@@ -890,20 +1877,34 @@ RSpec.describe RubySMB::Client do
890
1877
  smb2_client.smb2_ntlmssp_negotiate_packet
891
1878
  end
892
1879
 
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)
1880
+ it 'enables signing' do
1881
+ expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
899
1882
  end
900
1883
  end
901
1884
 
902
1885
  describe '#smb2_ntlmssp_negotiate' do
1886
+ before :example do
1887
+ allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
1888
+ allow(smb2_client).to receive(:send_recv)
1889
+ end
1890
+
903
1891
  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)
1892
+ expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
1893
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1894
+ smb2_client.smb2_ntlmssp_negotiate
1895
+ end
1896
+
1897
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1898
+ it "does not update the preauth hash with dialect #{dialect}" do
1899
+ smb2_client.dialect = dialect
1900
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1901
+ smb2_client.smb2_ntlmssp_negotiate
1902
+ end
1903
+ end
1904
+
1905
+ it "updates the preauth hash with dialect 0x0311" do
1906
+ smb2_client.dialect = '0x0311'
1907
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
907
1908
  smb2_client.smb2_ntlmssp_negotiate
908
1909
  end
909
1910
  end
@@ -961,13 +1962,35 @@ RSpec.describe RubySMB::Client do
961
1962
  it 'sets the session ID on the request packet' do
962
1963
  expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
963
1964
  end
1965
+
1966
+ it 'enables signing' do
1967
+ expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
1968
+ end
964
1969
  end
965
1970
 
966
1971
  describe '#smb2_ntlmssp_authenticate' do
1972
+ before :example do
1973
+ allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
1974
+ allow(smb2_client).to receive(:send_recv)
1975
+ end
1976
+
967
1977
  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)
1978
+ expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
1979
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1980
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1981
+ end
1982
+
1983
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1984
+ it "does not update the preauth hash with dialect #{dialect}" do
1985
+ smb2_client.dialect = dialect
1986
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1987
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1988
+ end
1989
+ end
1990
+
1991
+ it "updates the preauth hash with dialect 0x0311" do
1992
+ smb2_client.dialect = '0x0311'
1993
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
971
1994
  smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
972
1995
  end
973
1996
  end
@@ -1103,6 +2126,108 @@ RSpec.describe RubySMB::Client do
1103
2126
  end
1104
2127
  end
1105
2128
  end
2129
+
2130
+ describe '#smb3_sign' do
2131
+ context 'if signing is required and we have a session key' do
2132
+ let(:request) {
2133
+ packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
2134
+ packet.smb2_header.flags.signed = 1
2135
+ packet.smb2_header.signature = "\x00" * 16
2136
+ packet
2137
+ }
2138
+ let(:session_key) { 'Session Key' }
2139
+ before :example do
2140
+ smb3_client.session_key = session_key
2141
+ smb3_client.signing_required = true
2142
+ end
2143
+
2144
+ ['0x0300', '0x0302'].each do |dialect|
2145
+ context "with #{dialect} dialect" do
2146
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2147
+ smb3_client.dialect = dialect
2148
+ fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
2149
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2150
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2151
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2152
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2153
+ end
2154
+ end
2155
+ end
2156
+
2157
+ context "with 0x0311 dialect" do
2158
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2159
+ smb3_client.dialect = '0x0311'
2160
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2161
+ fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
2162
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2163
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2164
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2165
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2166
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2167
+ end
2168
+ end
2169
+
2170
+ context 'with an incompatible dialect' do
2171
+ it 'raises the expected exception' do
2172
+ smb3_client.dialect = '0x0202'
2173
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2174
+ RubySMB::Error::SigningError,
2175
+ 'Dialect is incompatible with SMBv3 signing'
2176
+ )
2177
+ end
2178
+ end
2179
+ end
2180
+
2181
+ context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
2182
+ let(:request) {
2183
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
2184
+ packet.smb2_header.flags.signed = 1
2185
+ packet.smb2_header.signature = "\x00" * 16
2186
+ packet
2187
+ }
2188
+ let(:session_key) { 'Session Key' }
2189
+ before :example do
2190
+ smb3_client.session_key = session_key
2191
+ smb3_client.signing_required = false
2192
+ end
2193
+
2194
+ ['0x0300', '0x0302'].each do |dialect|
2195
+ context "with #{dialect} dialect" do
2196
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2197
+ smb3_client.dialect = dialect
2198
+ fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
2199
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2200
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2201
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2202
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2203
+ end
2204
+ end
2205
+ end
2206
+
2207
+ context "with 0x0311 dialect" do
2208
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2209
+ smb3_client.dialect = '0x0311'
2210
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2211
+ fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
2212
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2213
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2214
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2215
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2216
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2217
+ end
2218
+ end
2219
+
2220
+ context 'with an incompatible dialect' do
2221
+ it 'raises the expected exception' do
2222
+ smb3_client.dialect = '0x0202'
2223
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2224
+ RubySMB::Error::SigningError,
2225
+ 'Dialect is incompatible with SMBv3 signing'
2226
+ )
2227
+ end
2228
+ end
2229
+ end
2230
+ end
1106
2231
  end
1107
2232
 
1108
2233
  context '#increment_smb_message_id' do
@@ -1156,7 +2281,10 @@ RSpec.describe RubySMB::Client do
1156
2281
 
1157
2282
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1158
2283
  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')
2284
+ expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
2285
+ RubySMB::Error::UnexpectedStatusCode,
2286
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2287
+ )
1160
2288
  end
1161
2289
 
1162
2290
  it 'creates a new Tree from itself, the share path, and the response packet' do
@@ -1177,11 +2305,14 @@ RSpec.describe RubySMB::Client do
1177
2305
  }
1178
2306
 
1179
2307
  describe '#smb2_tree_connect' do
1180
- it 'builds and sends a TreeconnectRequest for the supplied share' do
2308
+ it 'builds and sends the expected TreeconnectRequest for the supplied share' do
1181
2309
  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)
2310
+ expect(smb2_client).to receive(:send_recv) do |req|
2311
+ expect(req).to eq(request)
2312
+ expect(req.smb2_header.tree_id).to eq(65_535)
2313
+ expect(req.path).to eq(path.encode('UTF-16LE'))
2314
+ response.to_binary_s
2315
+ end
1185
2316
  smb2_client.smb2_tree_connect(path)
1186
2317
  end
1187
2318
 
@@ -1200,11 +2331,20 @@ RSpec.describe RubySMB::Client do
1200
2331
 
1201
2332
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1202
2333
  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')
2334
+ expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
2335
+ RubySMB::Error::UnexpectedStatusCode,
2336
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2337
+ )
1204
2338
  end
1205
2339
 
1206
2340
  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)
2341
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
2342
+ smb2_client.smb2_tree_from_response(path, response)
2343
+ end
2344
+
2345
+ it 'creates a new with encryption set if the response requires it' do
2346
+ response.share_flags.encrypt = 1
2347
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
1208
2348
  smb2_client.smb2_tree_from_response(path, response)
1209
2349
  end
1210
2350
  end
@@ -1301,7 +2441,7 @@ RSpec.describe RubySMB::Client do
1301
2441
  end
1302
2442
 
1303
2443
  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
2444
+ echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
1305
2445
  allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
1306
2446
  expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
1307
2447
  end
@@ -1325,5 +2465,346 @@ RSpec.describe RubySMB::Client do
1325
2465
  end
1326
2466
  end
1327
2467
 
2468
+ context 'Winreg' do
2469
+ describe '#connect_to_winreg' do
2470
+ let(:host) { '1.2.3.4' }
2471
+ let(:share) { "\\\\#{host}\\IPC$" }
2472
+ let(:ipc_tree) { double('IPC$ tree') }
2473
+ let(:named_pipe) { double('Named pipe') }
2474
+ before :example do
2475
+ allow(ipc_tree).to receive_messages(
2476
+ :share => share,
2477
+ :open_file => named_pipe
2478
+ )
2479
+ allow(client).to receive(:tree_connect).and_return(ipc_tree)
2480
+ end
2481
+
2482
+ context 'when the client is already connected to the IPC$ share' do
2483
+ before :example do
2484
+ client.tree_connects << ipc_tree
2485
+ allow(ipc_tree).to receive(:share).and_return(share)
2486
+ end
2487
+
2488
+ it 'does not connect to the already connected tree' do
2489
+ client.connect_to_winreg(host)
2490
+ expect(client).to_not have_received(:tree_connect)
2491
+ end
2492
+ end
2493
+
2494
+ it 'calls #tree_connect' do
2495
+ client.connect_to_winreg(host)
2496
+ expect(client).to have_received(:tree_connect).with(share)
2497
+ end
2498
+
2499
+ it 'open \'winreg\' file on the IPC$ Tree' do
2500
+ client.connect_to_winreg(host)
2501
+ expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
2502
+ end
2503
+
2504
+ it 'returns the expected opened named pipe' do
2505
+ expect(client.connect_to_winreg(host)).to eq(named_pipe)
2506
+ end
2507
+
2508
+ context 'when a block is given' do
2509
+ before :example do
2510
+ allow(named_pipe).to receive(:close)
2511
+ end
2512
+
2513
+ it 'yields the expected named_pipe' do
2514
+ client.connect_to_winreg(host) do |np|
2515
+ expect(np).to eq(named_pipe)
2516
+ end
2517
+ end
2518
+
2519
+ it 'closes the named pipe' do
2520
+ client.connect_to_winreg(host) { |np| }
2521
+ expect(named_pipe).to have_received(:close)
2522
+ end
2523
+
2524
+ it 'returns the block return value' do
2525
+ result = double('Result')
2526
+ expect(client.connect_to_winreg(host) { |np| result }).to eq(result)
2527
+ end
2528
+ end
2529
+ end
2530
+
2531
+ describe '#has_registry_key?' do
2532
+ let(:host) { '1.2.3.4' }
2533
+ let(:key) { 'HKLM\\Registry\\Key' }
2534
+ let(:named_pipe) { double('Named pipe') }
2535
+ let(:result) { double('Result') }
2536
+ before :example do
2537
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2538
+ allow(named_pipe).to receive(:has_registry_key?).and_return(result)
2539
+ end
2540
+
2541
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2542
+ client.has_registry_key?(host, key)
2543
+ expect(client).to have_received(:connect_to_winreg).with(host)
2544
+ end
2545
+
2546
+ it 'calls Pipe #has_registry_key?' do
2547
+ client.has_registry_key?(host, key)
2548
+ expect(named_pipe).to have_received(:has_registry_key?).with(key)
2549
+ end
2550
+ end
2551
+
2552
+ describe '#read_registry_key_value' do
2553
+ let(:host) { '1.2.3.4' }
2554
+ let(:key) { 'HKLM\\Registry\\Key' }
2555
+ let(:value_name) { 'Value' }
2556
+ let(:named_pipe) { double('Named pipe') }
2557
+ let(:result) { double('Result') }
2558
+ before :example do
2559
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2560
+ allow(named_pipe).to receive(:read_registry_key_value).and_return(result)
2561
+ end
2562
+
2563
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2564
+ client.read_registry_key_value(host, key, value_name)
2565
+ expect(client).to have_received(:connect_to_winreg).with(host)
2566
+ end
2567
+
2568
+ it 'calls Pipe #read_registry_key_value' do
2569
+ client.read_registry_key_value(host, key, value_name)
2570
+ expect(named_pipe).to have_received(:read_registry_key_value).with(key, value_name)
2571
+ end
2572
+ end
2573
+
2574
+ describe '#enum_registry_key' do
2575
+ let(:host) { '1.2.3.4' }
2576
+ let(:key) { 'HKLM\\Registry\\Key' }
2577
+ let(:named_pipe) { double('Named pipe') }
2578
+ let(:result) { double('Result') }
2579
+ before :example do
2580
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2581
+ allow(named_pipe).to receive(:enum_registry_key).and_return(result)
2582
+ end
2583
+
2584
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2585
+ client.enum_registry_key(host, key)
2586
+ expect(client).to have_received(:connect_to_winreg).with(host)
2587
+ end
2588
+
2589
+ it 'calls Pipe #enum_registry_key' do
2590
+ client.enum_registry_key(host, key)
2591
+ expect(named_pipe).to have_received(:enum_registry_key).with(key)
2592
+ end
2593
+ end
2594
+
2595
+ describe '#enum_registry_values' do
2596
+ let(:host) { '1.2.3.4' }
2597
+ let(:key) { 'HKLM\\Registry\\Key' }
2598
+ let(:named_pipe) { double('Named pipe') }
2599
+ let(:result) { double('Result') }
2600
+ before :example do
2601
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2602
+ allow(named_pipe).to receive(:enum_registry_values).and_return(result)
2603
+ end
2604
+
2605
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2606
+ client.enum_registry_values(host, key)
2607
+ expect(client).to have_received(:connect_to_winreg).with(host)
2608
+ end
2609
+
2610
+ it 'calls Pipe #enum_registry_values' do
2611
+ client.enum_registry_values(host, key)
2612
+ expect(named_pipe).to have_received(:enum_registry_values).with(key)
2613
+ end
2614
+ end
2615
+ end
2616
+
2617
+ describe '#update_preauth_hash' do
2618
+ it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
2619
+ expect { client.update_preauth_hash('Test') }.to raise_error(
2620
+ RubySMB::Error::EncryptionError,
2621
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
2622
+ )
2623
+ end
2624
+
2625
+ it 'computes the hash value' do
2626
+ packet = RubySMB::SMB2::Packet::EchoRequest.new
2627
+ data = 'Previous hash'
2628
+ algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
2629
+ RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
2630
+ ]
2631
+ client.preauth_integrity_hash_algorithm = algo
2632
+ client.preauth_integrity_hash_value = data
2633
+ hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
2634
+ client.update_preauth_hash(packet)
2635
+ expect(client.preauth_integrity_hash_value).to eq(hash)
2636
+ end
2637
+ end
2638
+
2639
+ context 'Encryption' do
2640
+ describe '#smb3_encrypt' do
2641
+ let(:transform_packet) { double('TransformHeader packet') }
2642
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2643
+ let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
2644
+
2645
+ before :example do
2646
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
2647
+ allow(transform_packet).to receive(:encrypt)
2648
+ client.session_key = session_key
2649
+ end
2650
+
2651
+ it 'does not generate a new client encryption key if it already exists' do
2652
+ client.client_encryption_key = 'key'
2653
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2654
+ expect(client.client_encryption_key).to eq('key')
2655
+ client.smb3_encrypt(data)
2656
+ end
2657
+
2658
+ ['0x0300', '0x0302'].each do |dialect|
2659
+ context "with #{dialect} dialect" do
2660
+ before :example do
2661
+ client.dialect = dialect
2662
+ end
2663
+
2664
+ it 'generates the client encryption key with the expected parameters' do
2665
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2666
+ session_key,
2667
+ "SMB2AESCCM\x00",
2668
+ "ServerIn \x00"
2669
+ ).and_call_original
2670
+ client.smb3_encrypt(data)
2671
+ end
2672
+ end
2673
+ end
2674
+
2675
+ context 'with 0x0311 dialect' do
2676
+ it 'generates the client encryption key with the expected parameters' do
2677
+ client.preauth_integrity_hash_value = ''
2678
+ client.dialect = '0x0311'
2679
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2680
+ session_key,
2681
+ "SMBC2SCipherKey\x00",
2682
+ ''
2683
+ ).and_call_original
2684
+ client.smb3_encrypt(data)
2685
+ end
2686
+ end
2687
+
2688
+ it 'raises the expected exception if the dialect is incompatible' do
2689
+ client.dialect = '0x0202'
2690
+ expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
2691
+ end
2692
+
2693
+ it 'creates a TransformHeader packet and encrypt the data' do
2694
+ client.dialect = '0x0300'
2695
+ client.encryption_algorithm = 'AES-128-CCM'
2696
+ client.session_id = 123
2697
+ client.smb3_encrypt(data)
2698
+ expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
2699
+ expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
2700
+ end
2701
+
2702
+ it 'generates the expected client encryption key with 0x0302 dialect' do
2703
+ client.dialect = '0x0302'
2704
+ expected_enc_key =
2705
+ "\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
2706
+ client.smb3_encrypt(data)
2707
+ expect(client.client_encryption_key).to eq expected_enc_key
2708
+ end
2709
+
2710
+ it 'generates the expected client encryption key with 0x0311 dialect' do
2711
+ client.dialect = '0x0311'
2712
+ client.session_key =
2713
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2714
+ client.preauth_integrity_hash_value =
2715
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2716
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2717
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2718
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2719
+ expected_enc_key =
2720
+ "\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
2721
+ client.smb3_encrypt(data)
2722
+ expect(client.client_encryption_key).to eq expected_enc_key
2723
+ end
2724
+ end
2725
+
2726
+ describe '#smb3_decrypt' do
2727
+ let(:transform_packet) { double('TransformHeader packet') }
2728
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2729
+
2730
+ before :example do
2731
+ allow(transform_packet).to receive(:decrypt)
2732
+ client.session_key = session_key
2733
+ end
2734
+
2735
+ it 'does not generate a new server encryption key if it already exists' do
2736
+ client.server_encryption_key = 'key'
2737
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2738
+ expect(client.server_encryption_key).to eq('key')
2739
+ client.smb3_decrypt(transform_packet)
2740
+ end
2741
+
2742
+ ['0x0300', '0x0302'].each do |dialect|
2743
+ context "with #{dialect} dialect" do
2744
+ before :example do
2745
+ client.dialect = dialect
2746
+ end
2747
+
2748
+ it 'generates the client encryption key with the expected parameters' do
2749
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2750
+ session_key,
2751
+ "SMB2AESCCM\x00",
2752
+ "ServerOut\x00"
2753
+ ).and_call_original
2754
+ client.smb3_decrypt(transform_packet)
2755
+ end
2756
+ end
2757
+ end
2758
+
2759
+ context 'with 0x0311 dialect' do
2760
+ it 'generates the client encryption key with the expected parameters' do
2761
+ client.preauth_integrity_hash_value = ''
2762
+ client.dialect = '0x0311'
2763
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2764
+ session_key,
2765
+ "SMBS2CCipherKey\x00",
2766
+ ''
2767
+ ).and_call_original
2768
+ client.smb3_decrypt(transform_packet)
2769
+ end
2770
+ end
2771
+
2772
+ it 'raises the expected exception if the dialect is incompatible' do
2773
+ client.dialect = '0x0202'
2774
+ expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
2775
+ end
2776
+
2777
+ it 'creates a TransformHeader packet and encrypt the data' do
2778
+ client.dialect = '0x0300'
2779
+ client.encryption_algorithm = 'AES-128-CCM'
2780
+ client.session_id = 123
2781
+ client.smb3_decrypt(transform_packet)
2782
+ expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
2783
+ end
2784
+
2785
+ it 'generates the expected server encryption key with 0x0302 dialect' do
2786
+ client.dialect = '0x0302'
2787
+ expected_enc_key =
2788
+ "\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
2789
+ client.smb3_decrypt(transform_packet)
2790
+ expect(client.server_encryption_key).to eq expected_enc_key
2791
+ end
2792
+
2793
+ it 'generates the expected server encryption key with 0x0311 dialect' do
2794
+ client.dialect = '0x0311'
2795
+ client.session_key =
2796
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2797
+ client.preauth_integrity_hash_value =
2798
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2799
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2800
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2801
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2802
+ expected_enc_key =
2803
+ "\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
2804
+ client.smb3_decrypt(transform_packet)
2805
+ expect(client.server_encryption_key).to eq expected_enc_key
2806
+ end
2807
+ end
2808
+ end
1328
2809
  end
1329
2810