ruby_smb 1.0.2 → 2.0.0

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 (200) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/enum_registry_key.rb +28 -0
  8. data/examples/enum_registry_values.rb +30 -0
  9. data/examples/negotiate.rb +51 -8
  10. data/examples/pipes.rb +2 -1
  11. data/examples/read_file_encryption.rb +56 -0
  12. data/examples/read_registry_key_value.rb +32 -0
  13. data/lib/ruby_smb.rb +4 -1
  14. data/lib/ruby_smb/client.rb +200 -20
  15. data/lib/ruby_smb/client/authentication.rb +70 -33
  16. data/lib/ruby_smb/client/echo.rb +20 -2
  17. data/lib/ruby_smb/client/encryption.rb +62 -0
  18. data/lib/ruby_smb/client/negotiation.rb +160 -24
  19. data/lib/ruby_smb/client/signing.rb +19 -0
  20. data/lib/ruby_smb/client/tree_connect.rb +24 -18
  21. data/lib/ruby_smb/client/utils.rb +8 -7
  22. data/lib/ruby_smb/client/winreg.rb +46 -0
  23. data/lib/ruby_smb/crypto.rb +30 -0
  24. data/lib/ruby_smb/dcerpc.rb +38 -0
  25. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  27. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  28. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  29. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  30. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  31. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  33. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  34. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  36. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  40. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  44. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  48. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  49. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  50. data/lib/ruby_smb/dispatcher/socket.rb +5 -3
  51. data/lib/ruby_smb/error.rb +68 -2
  52. data/lib/ruby_smb/generic_packet.rb +33 -4
  53. data/lib/ruby_smb/smb1/commands.rb +1 -1
  54. data/lib/ruby_smb/smb1/file.rb +66 -15
  55. data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
  56. data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
  57. data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
  58. data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
  59. data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
  60. data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
  61. data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
  62. data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
  63. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
  64. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
  65. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
  66. data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
  67. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
  68. data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
  69. data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
  70. data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
  71. data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
  72. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
  73. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
  74. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +3 -2
  75. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
  76. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +3 -2
  77. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
  78. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
  79. data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
  80. data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
  81. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
  83. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
  84. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
  85. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
  86. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
  87. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
  88. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
  89. data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
  90. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
  91. data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
  92. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
  93. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
  94. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
  95. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
  96. data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
  97. data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
  98. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +3 -6
  99. data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
  100. data/lib/ruby_smb/smb1/pipe.rb +87 -6
  101. data/lib/ruby_smb/smb1/tree.rb +41 -3
  102. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  103. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  104. data/lib/ruby_smb/smb2/file.rb +103 -25
  105. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  106. data/lib/ruby_smb/smb2/packet.rb +2 -0
  107. data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
  108. data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
  109. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  110. data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
  111. data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
  112. data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
  113. data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
  114. data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
  115. data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
  116. data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
  117. data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
  118. data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
  119. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -17
  120. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +51 -4
  121. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
  122. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
  123. data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
  124. data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
  125. data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
  126. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
  127. data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
  128. data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
  129. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  130. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +93 -10
  131. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +10 -22
  132. data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
  133. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
  134. data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
  135. data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
  136. data/lib/ruby_smb/smb2/pipe.rb +86 -12
  137. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  138. data/lib/ruby_smb/smb2/tree.rb +65 -21
  139. data/lib/ruby_smb/version.rb +1 -1
  140. data/ruby_smb.gemspec +5 -3
  141. data/spec/lib/ruby_smb/client_spec.rb +1563 -104
  142. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  143. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  146. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  147. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  148. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  149. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  150. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  151. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  152. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  153. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  154. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  155. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  156. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  157. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  158. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  159. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  160. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  161. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  162. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  163. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  164. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  165. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  166. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  167. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +3 -1
  168. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  169. data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
  170. data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
  171. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
  172. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  173. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  174. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  175. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  176. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
  177. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
  178. data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
  179. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
  180. data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
  181. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  182. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  183. data/spec/lib/ruby_smb/smb2/file_spec.rb +323 -6
  184. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  185. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  186. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
  187. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  188. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  189. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
  190. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  191. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  192. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -22
  193. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +286 -149
  194. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  195. data/spec/lib/ruby_smb/smb2/tree_spec.rb +261 -2
  196. metadata +191 -83
  197. metadata.gz.sig +0 -0
  198. data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
  199. data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
  200. data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
@@ -4,6 +4,16 @@ module RubySMB
4
4
  # An SMB2 TreeConnectResponse Packet as defined in
5
5
  # [2.2.10 SMB2 TREE_CONNECT Response](https://msdn.microsoft.com/en-us/library/cc246499.aspx)
6
6
  class TreeConnectResponse < RubySMB::GenericPacket
7
+ COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
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
+
7
17
  endian :little
8
18
  smb2_header :smb2_header
9
19
  uint16 :structure_size, label: 'Structure Size', initial_value: 16
@@ -15,31 +25,9 @@ module RubySMB
15
25
 
16
26
  def initialize_instance
17
27
  super
18
- smb2_header.command = RubySMB::SMB2::Commands::TREE_CONNECT
19
28
  smb2_header.flags.reply = 1
20
29
  end
21
30
 
22
- # Returns the ACCESS_MASK for the Maximal Share Access Rights. The packet
23
- # defaults this to a {RubySMB::SMB2::BitField::DirectoryAccessMask}. If it is anything other than
24
- # a directory that has been connected to, it will re-cast it as a {RubySMB::SMB2::BitField::FileAccessMask}
25
- #
26
- # @return [RubySMB::SMB2::BitField::DirectoryAccessMask] if a directory was connected to
27
- # @return [RubySMB::SMB2::BitField::FileAccessMask] if anything else was connected to
28
- def access_rights
29
- if is_directory?
30
- maximal_access
31
- else
32
- mask = maximal_access.to_binary_s
33
- RubySMB::SMB2::BitField::FileAccessMask.read(mask)
34
- end
35
- end
36
-
37
- # Checks if the remote Tree is a directory
38
- #
39
- # @return [Boolean]
40
- def is_directory?
41
- share_type == 0x01
42
- end
43
31
  end
44
32
  end
45
33
  end
@@ -4,15 +4,13 @@ module RubySMB
4
4
  # An SMB2 TreeDisconnectRequest Packet as defined in
5
5
  # [2.2.11 SMB2 TREE_DISCONNECT Request](https://msdn.microsoft.com/en-us/library/cc246500.aspx)
6
6
  class TreeDisconnectRequest < RubySMB::GenericPacket
7
+ COMMAND = RubySMB::SMB2::Commands::TREE_DISCONNECT
8
+
7
9
  endian :little
8
10
  smb2_header :smb2_header
9
11
  uint16 :structure_size, label: 'Structure Size', initial_value: 4
10
12
  uint16 :reserved, label: 'Reserved', initial_value: 0
11
13
 
12
- def initialize_instance
13
- super
14
- smb2_header.command = RubySMB::SMB2::Commands::TREE_DISCONNECT
15
- end
16
14
  end
17
15
  end
18
16
  end
@@ -4,13 +4,14 @@ module RubySMB
4
4
  # An SMB2 TreeDisconnectResponse Packet as defined in
5
5
  # [2.2.12 SMB2 TREE_DISCONNECT Response](https://msdn.microsoft.com/en-us/library/cc246501.aspx)
6
6
  class TreeDisconnectResponse < RubySMB::GenericPacket
7
+ COMMAND = RubySMB::SMB2::Commands::TREE_DISCONNECT
8
+
7
9
  endian :little
8
10
  smb2_header :smb2_header
9
11
  uint16 :structure_size, label: 'Structure Size', initial_value: 4
10
12
 
11
13
  def initialize_instance
12
14
  super
13
- smb2_header.command = RubySMB::SMB2::Commands::TREE_DISCONNECT
14
15
  smb2_header.flags.reply = 1
15
16
  end
16
17
  end
@@ -4,6 +4,8 @@ module RubySMB
4
4
  # An SMB2 Write Request Packet as defined in
5
5
  # [2.2.21 SMB2 WRITE Request](https://msdn.microsoft.com/en-us/library/cc246532.aspx)
6
6
  class WriteRequest < RubySMB::GenericPacket
7
+ COMMAND = RubySMB::SMB2::Commands::WRITE
8
+
7
9
  endian :little
8
10
 
9
11
  smb2_header :smb2_header
@@ -19,10 +21,6 @@ module RubySMB
19
21
  uint32 :flags, label: 'Flags'
20
22
  string :buffer, label: 'Write Data Buffer'
21
23
 
22
- def initialize_instance
23
- super
24
- smb2_header.command = RubySMB::SMB2::Commands::WRITE
25
- end
26
24
  end
27
25
  end
28
26
  end
@@ -4,6 +4,8 @@ module RubySMB
4
4
  # An SMB2 Write Response Packet as defined in
5
5
  # [2.2.22 SMB2 WRITE Response](https://msdn.microsoft.com/en-us/library/cc246533.aspx)
6
6
  class WriteResponse < RubySMB::GenericPacket
7
+ COMMAND = RubySMB::SMB2::Commands::WRITE
8
+
7
9
  endian :little
8
10
 
9
11
  smb2_header :smb2_header
@@ -17,7 +19,6 @@ module RubySMB
17
19
 
18
20
  def initialize_instance
19
21
  super
20
- smb2_header.command = RubySMB::SMB2::Commands::WRITE
21
22
  smb2_header.flags.reply = 1
22
23
  end
23
24
  end
@@ -3,18 +3,29 @@ 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
+ end
21
+ super(tree: tree, response: response, name: name)
22
+ end
23
+
13
24
  # Performs a peek operation on the named pipe
14
25
  #
15
26
  # @param peek_size [Integer] Amount of data to peek
16
27
  # @return [RubySMB::SMB2::Packet::IoctlResponse]
17
- # @raise [RubySMB::Error::InvalidPacket] if not a valid FSCTL_PIPE_PEEK response
28
+ # @raise [RubySMB::Error::InvalidPacket] if not a valid FIoctlResponse response
18
29
  # @raise [RubySMB::Error::UnexpectedStatusCode] If status is not STATUS_BUFFER_OVERFLOW or STATUS_SUCCESS
19
30
  def peek(peek_size: 0)
20
31
  packet = RubySMB::SMB2::Packet::IoctlRequest.new
@@ -24,18 +35,18 @@ module RubySMB
24
35
  packet.max_output_response = 16 + peek_size
25
36
  packet = set_header_fields(packet)
26
37
  raw_response = @tree.client.send_recv(packet)
27
- begin
28
- response = RubySMB::SMB2::Packet::IoctlResponse.read(raw_response)
29
- rescue IOError
30
- response = RubySMB::SMB2::Packet::ErrorPacket.read(raw_response)
38
+ response = RubySMB::SMB2::Packet::IoctlResponse.read(raw_response)
39
+ unless response.valid?
40
+ raise RubySMB::Error::InvalidPacket.new(
41
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
42
+ expected_cmd: RubySMB::SMB2::Packet::IoctlResponse::COMMAND,
43
+ received_proto: response.smb2_header.protocol,
44
+ received_cmd: response.smb2_header.command
45
+ )
31
46
  end
32
47
 
33
48
  unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
34
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
35
- end
36
-
37
- unless response.smb2_header.command == RubySMB::SMB2::Commands::IOCTL
38
- raise RubySMB::Error::InvalidPacket, 'Not an IoctlResponse packet'
49
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
39
50
  end
40
51
  response
41
52
  end
@@ -67,6 +78,69 @@ module RubySMB
67
78
  state == STATUS_CONNECTED
68
79
  end
69
80
 
81
+ def dcerpc_request(stub_packet, options={})
82
+ options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
83
+ dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
84
+ dcerpc_request.stub.read(stub_packet.to_binary_s)
85
+ ioctl_send_recv(dcerpc_request, options)
86
+ end
87
+
88
+ def ioctl_send_recv(action, options={})
89
+ request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
90
+ request.ctl_code = 0x0011C017
91
+ request.flags.is_fsctl = 0x00000001
92
+ request.buffer = action.to_binary_s
93
+
94
+ ioctl_raw_response = @tree.client.send_recv(request)
95
+ ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
96
+ unless ioctl_response.valid?
97
+ raise RubySMB::Error::InvalidPacket.new(
98
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
99
+ expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
100
+ received_proto: ioctl_response.smb2_header.protocol,
101
+ received_cmd: ioctl_response.smb2_header.command
102
+ )
103
+ end
104
+ unless [WindowsError::NTStatus::STATUS_SUCCESS,
105
+ WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
106
+ raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
107
+ end
108
+
109
+ raw_data = ioctl_response.output_data
110
+ if ioctl_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
111
+ raw_data << read(bytes: @tree.client.max_buffer_size - ioctl_response.output_count)
112
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
113
+ unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
114
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
115
+ end
116
+ stub_data = dcerpc_response.stub.to_s
117
+
118
+ loop do
119
+ break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
120
+ raw_data = read(bytes: @tree.client.max_buffer_size)
121
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
122
+ stub_data << dcerpc_response.stub.to_s
123
+ end
124
+ stub_data
125
+ else
126
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
127
+ dcerpc_response.stub.to_s
128
+ end
129
+ end
130
+
131
+
132
+ private
133
+
134
+ def dcerpc_response_from_raw_response(raw_data)
135
+ dcerpc_response = RubySMB::Dcerpc::Response.read(raw_data)
136
+ unless dcerpc_response.pdu_header.ptype == RubySMB::Dcerpc::PTypes::RESPONSE
137
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not a Response packet"
138
+ end
139
+ dcerpc_response
140
+ rescue IOError
141
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Error reading the DCERPC response"
142
+ end
143
+
70
144
  end
71
145
  end
72
146
  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,22 +23,37 @@ 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 encryption is required (SMB 3.x)
27
+ # @!attribute [rw] encryption_required
28
+ # @return [Boolean]
29
+ attr_accessor :encryption_required
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
+ @encryption_required = encrypt
32
38
  end
33
39
 
34
40
  # Disconnects this Tree from the current session
35
41
  #
36
42
  # @return [WindowsError::ErrorCode] the NTStatus sent back by the server.
43
+ # @raise [RubySMB::Error::InvalidPacket] if the response is not a TreeDisconnectResponse packet
37
44
  def disconnect!
38
45
  request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
39
46
  request = set_header_fields(request)
40
- raw_response = client.send_recv(request)
47
+ raw_response = client.send_recv(request, encrypt: @encryption_required)
41
48
  response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
49
+ unless response.valid?
50
+ raise RubySMB::Error::InvalidPacket.new(
51
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
52
+ expected_cmd: RubySMB::SMB2::Packet::TreeDisconnectResponse::COMMAND,
53
+ received_proto: response.smb2_header.protocol,
54
+ received_cmd: response.smb2_header.command
55
+ )
56
+ end
42
57
  response.status_code
43
58
  end
44
59
 
@@ -87,22 +102,29 @@ module RubySMB
87
102
  create_request.create_disposition = disposition
88
103
  create_request.name = filename
89
104
 
90
- raw_response = client.send_recv(create_request)
105
+ raw_response = client.send_recv(create_request, encrypt: @encryption_required)
91
106
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
92
-
93
- if response.is_a?(RubySMB::SMB2::Packet::ErrorPacket)
94
- raise RubySMB::Error::RubySMBError
107
+ unless response.valid?
108
+ raise RubySMB::Error::InvalidPacket.new(
109
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
110
+ expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
111
+ received_proto: response.smb2_header.protocol,
112
+ received_cmd: response.smb2_header.command
113
+ )
114
+ end
115
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
116
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
95
117
  end
96
118
 
97
119
  case @share_type
98
- when 0x01
99
- RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
100
- when 0x02
120
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
121
+ RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @encryption_required)
122
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
101
123
  RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
102
- # when 0x03
124
+ # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
103
125
  # it's a printer!
104
126
  else
105
- raise RubySMB::Error::RubySMBError
127
+ raise RubySMB::Error::RubySMBError, 'Unsupported share type'
106
128
  end
107
129
  end
108
130
 
@@ -116,6 +138,7 @@ module RubySMB
116
138
  # @param pattern [String] search pattern
117
139
  # @param type [Class] file information class
118
140
  # @return [Array] array of directory structures
141
+ # @raise [RubySMB::Error::InvalidPacket] if the response is not a QueryDirectoryResponse packet
119
142
  def list(directory: nil, pattern: '*', type: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation)
120
143
  create_response = open_directory(directory: directory)
121
144
  file_id = create_response.file_id
@@ -131,16 +154,23 @@ module RubySMB
131
154
  files = []
132
155
 
133
156
  loop do
134
- response = client.send_recv(directory_request)
157
+ response = client.send_recv(directory_request, encrypt: @encryption_required)
135
158
  directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
159
+ unless directory_response.valid?
160
+ raise RubySMB::Error::InvalidPacket.new(
161
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
162
+ expected_cmd: RubySMB::SMB2::Packet::QueryDirectoryResponse::COMMAND,
163
+ received_proto: directory_response.smb2_header.protocol,
164
+ received_cmd: directory_response.smb2_header.command
165
+ )
166
+ end
136
167
 
137
168
  status_code = directory_response.smb2_header.nt_status.to_nt_status
138
169
 
139
170
  break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
140
171
 
141
172
  unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
142
-
143
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
173
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
144
174
  end
145
175
 
146
176
  files += directory_response.results(type)
@@ -162,14 +192,28 @@ module RubySMB
162
192
  # @param write [Boolean] whether to request write access
163
193
  # @param delete [Boolean] whether to request delete access
164
194
  # @return [RubySMB::SMB2::Packet::CreateResponse] the response packet returned from the server
195
+ # @raise [RubySMB::Error::InvalidPacket] if the response is not a CreateResponse packet
165
196
  def open_directory(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
166
197
  impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE,
167
198
  read: true, write: false, delete: false)
168
199
 
169
200
  create_request = open_directory_packet(directory: directory, disposition: disposition,
170
201
  impersonation: impersonation, read: read, write: write, delete: delete)
171
- raw_response = client.send_recv(create_request)
172
- RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
202
+ raw_response = client.send_recv(create_request, encrypt: @encryption_required)
203
+ response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
204
+ unless response.valid?
205
+ raise RubySMB::Error::InvalidPacket.new(
206
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
207
+ expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
208
+ received_proto: response.smb2_header.protocol,
209
+ received_cmd: response.smb2_header.command
210
+ )
211
+ end
212
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
213
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
214
+ end
215
+
216
+ response
173
217
  end
174
218
 
175
219
  # Creates the Packet for the #open_directory method.
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '1.0.2'.freeze
2
+ VERSION = '2.0.0'.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,9 +6,65 @@ 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 }
13
+ let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
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 :encryption_required }
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 }
12
68
 
13
69
  describe '#initialize' do
14
70
  it 'should raise an ArgumentError without a valid dispatcher' do
@@ -25,14 +81,21 @@ RSpec.describe RubySMB::Client do
25
81
 
26
82
  it 'accepts an argument to disable smb1 support' do
27
83
  expect(smb2_client.smb1).to be false
84
+ expect(smb3_client.smb1).to be false
28
85
  end
29
86
 
30
87
  it 'accepts an argument to disable smb2 support' do
31
88
  expect(smb1_client.smb2).to be false
89
+ expect(smb3_client.smb2).to be false
32
90
  end
33
91
 
34
- it 'raises an exception if both SMB1 and SMB2 are disabled' do
35
- expect { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
92
+ it 'accepts an argument to disable smb3 support' do
93
+ expect(smb1_client.smb3).to be false
94
+ expect(smb2_client.smb3).to be false
95
+ end
96
+
97
+ it 'raises an exception if SMB1, SMB2 and SMB3 are disabled' do
98
+ 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')
36
99
  end
37
100
 
38
101
  it 'sets the username attribute' do
@@ -43,6 +106,11 @@ RSpec.describe RubySMB::Client do
43
106
  expect(client.password).to eq password
44
107
  end
45
108
 
109
+ it 'sets the encryption_required attribute' do
110
+ client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
111
+ expect(client.encryption_required).to eq true
112
+ end
113
+
46
114
  it 'creates an NTLM client' do
47
115
  expect(client.ntlm_client).to be_a Net::NTLM::Client
48
116
  end
@@ -75,13 +143,52 @@ RSpec.describe RubySMB::Client do
75
143
  end
76
144
  end
77
145
 
146
+ describe '#echo' do
147
+ let(:response) { double('Echo Response') }
148
+ before :example do
149
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
150
+ end
151
+
152
+ context 'with SMB1' do
153
+ it 'calls #smb1_echo with the expected arguments' do
154
+ allow(smb1_client).to receive(:smb1_echo).and_return(response)
155
+ count = 3
156
+ data = 'testing...'
157
+ smb1_client.echo(count: count, data: data)
158
+ expect(smb1_client).to have_received(:smb1_echo).with(count: count, data: data)
159
+ end
160
+ end
161
+
162
+ context 'with SMB2' do
163
+ it 'calls #smb2_echo without arguments' do
164
+ allow(smb2_client).to receive(:smb2_echo).and_return(response)
165
+ smb2_client.echo
166
+ expect(smb2_client).to have_received(:smb2_echo)
167
+ end
168
+ end
169
+
170
+ context 'with SMB3' do
171
+ it 'calls #smb2_echo without arguments' do
172
+ allow(smb3_client).to receive(:smb2_echo).and_return(response)
173
+ smb3_client.echo
174
+ expect(smb3_client).to have_received(:smb2_echo)
175
+ end
176
+ end
177
+
178
+ it 'returns the expected status code' do
179
+ allow(smb2_client).to receive(:smb2_echo).and_return(response)
180
+ expect(smb2_client.echo).to eq(WindowsError::NTStatus::STATUS_SUCCESS)
181
+ end
182
+ end
183
+
78
184
  describe '#send_recv' do
79
185
  let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
80
186
  let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
81
187
 
82
188
  before(:each) do
83
- expect(dispatcher).to receive(:send_packet).and_return(nil)
84
- expect(dispatcher).to receive(:recv_packet).and_return('A')
189
+ allow(client).to receive(:is_status_pending?).and_return(false)
190
+ allow(dispatcher).to receive(:send_packet).and_return(nil)
191
+ allow(dispatcher).to receive(:recv_packet).and_return('A')
85
192
  end
86
193
 
87
194
  it 'checks the packet version' do
@@ -89,14 +196,218 @@ RSpec.describe RubySMB::Client do
89
196
  client.send_recv(smb1_request)
90
197
  end
91
198
 
92
- it 'calls #smb1_sign if it is an SMB1 packet' do
93
- expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
199
+ context 'when signing' do
200
+ it 'calls #smb1_sign if it is an SMB1 packet' do
201
+ expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
202
+ client.send_recv(smb1_request)
203
+ end
204
+
205
+ context 'with an SMB2 packet' do
206
+ it 'does not sign a SessionSetupRequest packet' do
207
+ expect(smb2_client).to_not receive(:smb2_sign)
208
+ expect(smb2_client).to_not receive(:smb3_sign)
209
+ client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
210
+ end
211
+
212
+ it 'calls #smb2_sign if it is an SMB2 client' do
213
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
214
+ expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
215
+ smb2_client.send_recv(smb2_request)
216
+ end
217
+
218
+ it 'calls #smb3_sign if it is an SMB3 client' do
219
+ allow(smb3_client).to receive(:is_status_pending?).and_return(false)
220
+ expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
221
+ smb3_client.send_recv(smb2_request)
222
+ end
223
+ end
224
+ end
225
+
226
+ it 'sends the expected packet and gets the response' do
227
+ expect(dispatcher).to receive(:send_packet).with(smb1_request)
228
+ expect(dispatcher).to receive(:recv_packet)
94
229
  client.send_recv(smb1_request)
95
230
  end
96
231
 
97
- it 'calls #smb2_sign if it is an SMB2 packet' do
98
- expect(client).to receive(:smb2_sign).with(smb2_request).and_call_original
99
- client.send_recv(smb2_request)
232
+ context 'with SMB1' do
233
+ it 'does not check if it is a STATUS_PENDING response' do
234
+ expect(smb1_client).to_not receive(:is_status_pending?)
235
+ smb1_client.send_recv(smb1_request)
236
+ end
237
+ end
238
+
239
+ context 'with SMB2' do
240
+ context 'when receiving a STATUS_PENDING response' do
241
+ it 'waits 1 second and reads/decrypts again' do
242
+ allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
243
+ expect(smb2_client).to receive(:sleep).with(1)
244
+ expect(dispatcher).to receive(:recv_packet).twice
245
+ smb2_client.send_recv(smb2_request)
246
+ end
247
+ end
248
+ end
249
+
250
+ context 'with SMB3 and encryption' do
251
+ before :example do
252
+ smb3_client.dialect = '0x0300'
253
+ allow(smb3_client).to receive(:is_status_pending?).and_return(false)
254
+ end
255
+
256
+ context 'with a SessionSetupRequest' do
257
+ it 'does not encrypt/decrypt' do
258
+ request = RubySMB::SMB2::Packet::SessionSetupRequest.new
259
+ expect(smb3_client).to_not receive(:send_encrypt).with(request)
260
+ expect(smb3_client).to_not receive(:recv_encrypt)
261
+ expect(dispatcher).to receive(:send_packet).with(request)
262
+ expect(dispatcher).to receive(:recv_packet)
263
+ smb3_client.send_recv(request)
264
+ end
265
+ end
266
+
267
+ context 'with a NegotiateRequest' do
268
+ it 'does not encrypt/decrypt' do
269
+ request = RubySMB::SMB2::Packet::NegotiateRequest.new
270
+ expect(smb3_client).to_not receive(:send_encrypt).with(request)
271
+ expect(smb3_client).to_not receive(:recv_encrypt)
272
+ expect(dispatcher).to receive(:send_packet).with(request)
273
+ expect(dispatcher).to receive(:recv_packet)
274
+ smb3_client.send_recv(request)
275
+ end
276
+ end
277
+
278
+ it 'encrypts and decrypts' do
279
+ expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
280
+ expect(smb3_client).to receive(:recv_encrypt)
281
+ smb3_client.send_recv(smb2_request)
282
+ end
283
+
284
+ context 'when receiving a STATUS_PENDING response' do
285
+ it 'waits 1 second and reads/decrypts again' do
286
+ allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
287
+ expect(smb3_client).to receive(:sleep).with(1)
288
+ expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
289
+ expect(smb3_client).to receive(:recv_encrypt).twice
290
+ smb3_client.send_recv(smb2_request)
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ describe '#is_status_pending?' do
297
+ let(:response) {
298
+ res = RubySMB::SMB2::Packet::SessionSetupRequest.new
299
+ res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
300
+ res.smb2_header.flags.async_command = 1
301
+ res
302
+ }
303
+
304
+ it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
305
+ expect(client.is_status_pending?(response.to_binary_s)).to be true
306
+ end
307
+
308
+ it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
309
+ response.smb2_header.flags.async_command = 0
310
+ expect(client.is_status_pending?(response.to_binary_s)).to be false
311
+ end
312
+
313
+ it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
314
+ response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
315
+ expect(client.is_status_pending?(response.to_binary_s)).to be false
316
+ end
317
+ end
318
+
319
+ describe '#can_be_encrypted?' do
320
+ it 'returns true if the packet can be encrypted' do
321
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
322
+ expect(client.can_be_encrypted?(packet)).to be true
323
+ end
324
+
325
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
326
+ it "returns false if the packet is a #{klass}" do
327
+ packet = klass.new
328
+ expect(client.can_be_encrypted?(packet)).to be false
329
+ end
330
+ end
331
+ end
332
+
333
+ describe '#encryption_supported?' do
334
+ ['0x0300', '0x0302', '0x0311'].each do |dialect|
335
+ it "returns true if the dialect is #{dialect}" do
336
+ client.dialect = dialect
337
+ expect(client.encryption_supported?).to be true
338
+ end
339
+ end
340
+
341
+ it "returns false if the dialect does not support encryption" do
342
+ client.dialect = '0x0202'
343
+ expect(client.encryption_supported?).to be false
344
+ end
345
+ end
346
+
347
+ describe '#send_encrypt' do
348
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
349
+ before :example do
350
+ allow(dispatcher).to receive(:send_packet)
351
+ client.dialect = '0x0300'
352
+ end
353
+
354
+ it 'creates a Transform request' do
355
+ expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
356
+ client.send_encrypt(packet)
357
+ end
358
+
359
+ it 'raises an EncryptionError exception if an error occurs while encrypting' do
360
+ allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
361
+ expect { client.send_encrypt(packet) }.to raise_error(
362
+ RubySMB::Error::EncryptionError,
363
+ "Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
364
+ )
365
+ end
366
+
367
+ it 'sends the encrypted packet' do
368
+ encrypted_packet = double('Encrypted packet')
369
+ allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
370
+ client.send_encrypt(packet)
371
+ expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
372
+ end
373
+ end
374
+
375
+ describe '#recv_encrypt' do
376
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
377
+ before :example do
378
+ allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
379
+ client.dialect = '0x0300'
380
+ allow(client).to receive(:smb3_decrypt)
381
+ end
382
+
383
+ it 'reads the response packet' do
384
+ client.recv_encrypt
385
+ expect(dispatcher).to have_received(:recv_packet)
386
+ end
387
+
388
+ it 'parses the response as a Transform response packet' do
389
+ expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
390
+ client.recv_encrypt
391
+ end
392
+
393
+ it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
394
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
395
+ expect { client.recv_encrypt}.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
396
+ end
397
+
398
+ it 'decrypts the Transform response packet' do
399
+ transform = double('Transform header packet')
400
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
401
+ client.recv_encrypt
402
+ expect(client).to have_received(:smb3_decrypt).with(transform)
403
+ end
404
+
405
+ it 'raises an EncryptionError exception if an error occurs while decrypting' do
406
+ allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError )
407
+ expect { client.recv_encrypt}.to raise_error(
408
+ RubySMB::Error::EncryptionError,
409
+ "Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError"
410
+ )
100
411
  end
101
412
  end
102
413
 
@@ -153,6 +464,132 @@ RSpec.describe RubySMB::Client do
153
464
  end
154
465
  end
155
466
 
467
+ describe '#logoff!' do
468
+ context 'with SMB1' do
469
+ let(:raw_response) { double('Raw response') }
470
+ let(:logoff_response) {
471
+ RubySMB::SMB1::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB1::Commands::SMB_COM_LOGOFF} )
472
+ }
473
+ before :example do
474
+ allow(smb1_client).to receive(:send_recv).and_return(raw_response)
475
+ allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
476
+ allow(smb1_client).to receive(:wipe_state!)
477
+ end
478
+
479
+ it 'creates a LogoffRequest packet' do
480
+ expect(RubySMB::SMB1::Packet::LogoffRequest).to receive(:new).and_call_original
481
+ smb1_client.logoff!
482
+ end
483
+
484
+ it 'calls #send_recv' do
485
+ expect(smb1_client).to receive(:send_recv)
486
+ smb1_client.logoff!
487
+ end
488
+
489
+ it 'reads the raw response as a LogoffResponse packet' do
490
+ expect(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).with(raw_response)
491
+ smb1_client.logoff!
492
+ end
493
+
494
+ it 'raise an InvalidPacket exception when the response is an empty packet' do
495
+ allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB1::Packet::EmptyPacket.new)
496
+ expect {smb1_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
497
+ end
498
+
499
+ it 'raise an InvalidPacket exception when the response is not valid' do
500
+ allow(logoff_response).to receive(:valid?).and_return(false)
501
+ expect {smb1_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
502
+ end
503
+
504
+ it 'calls #wipe_state!' do
505
+ expect(smb1_client).to receive(:wipe_state!)
506
+ smb1_client.logoff!
507
+ end
508
+
509
+ it 'returns the expected status code' do
510
+ logoff_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_PENDING.value
511
+ allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
512
+ expect(smb1_client.logoff!).to eq(WindowsError::NTStatus::STATUS_PENDING)
513
+ end
514
+ end
515
+
516
+ context 'with SMB2' do
517
+ let(:raw_response) { double('Raw response') }
518
+ let(:logoff_response) {
519
+ RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
520
+ }
521
+ before :example do
522
+ allow(smb2_client).to receive(:send_recv).and_return(raw_response)
523
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
524
+ allow(smb2_client).to receive(:wipe_state!)
525
+ end
526
+
527
+ it 'creates a LogoffRequest packet' do
528
+ expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
529
+ smb2_client.logoff!
530
+ end
531
+
532
+ it 'calls #send_recv' do
533
+ expect(smb2_client).to receive(:send_recv)
534
+ smb2_client.logoff!
535
+ end
536
+
537
+ it 'reads the raw response as a LogoffResponse packet' do
538
+ expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
539
+ smb2_client.logoff!
540
+ end
541
+
542
+ it 'raise an InvalidPacket exception when the response is an error packet' do
543
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
544
+ expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
545
+ end
546
+
547
+ it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
548
+ logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
549
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
550
+ expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
551
+ end
552
+ end
553
+
554
+ context 'with SMB3' do
555
+ let(:raw_response) { double('Raw response') }
556
+ let(:logoff_response) {
557
+ RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
558
+ }
559
+ before :example do
560
+ allow(smb3_client).to receive(:send_recv).and_return(raw_response)
561
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
562
+ allow(smb3_client).to receive(:wipe_state!)
563
+ end
564
+
565
+ it 'creates a LogoffRequest packet' do
566
+ expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
567
+ smb3_client.logoff!
568
+ end
569
+
570
+ it 'calls #send_recv' do
571
+ expect(smb3_client).to receive(:send_recv)
572
+ smb3_client.logoff!
573
+ end
574
+
575
+ it 'reads the raw response as a LogoffResponse packet' do
576
+ expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
577
+ smb3_client.logoff!
578
+ end
579
+
580
+ it 'raise an InvalidPacket exception when the response is an error packet' do
581
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
582
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
583
+ end
584
+
585
+ it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
586
+ logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
587
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
588
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
589
+ end
590
+ end
591
+ end
592
+
156
593
  context 'NetBIOS Session Service' do
157
594
  describe '#session_request' do
158
595
  let(:session_header) { RubySMB::Nbss::SessionHeader.new }
@@ -197,6 +634,11 @@ RSpec.describe RubySMB::Client do
197
634
  allow(dispatcher).to receive(:recv_packet).and_return(negative_session_response.to_binary_s)
198
635
  expect { client.session_request }.to raise_error(RubySMB::Error::NetBiosSessionService)
199
636
  end
637
+
638
+ it 'raises an InvalidPacket exception when an error occurs while reading' do
639
+ allow(RubySMB::Nbss::SessionHeader).to receive(:read).and_raise(IOError)
640
+ expect { client.session_request }.to raise_error(RubySMB::Error::InvalidPacket)
641
+ end
200
642
  end
201
643
 
202
644
  describe '#session_request_packet' do
@@ -271,7 +713,8 @@ RSpec.describe RubySMB::Client do
271
713
  smb1_extended_response.to_binary_s
272
714
  }
273
715
 
274
- let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
716
+ let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
717
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
275
718
 
276
719
  describe '#smb1_negotiate_request' do
277
720
  it 'returns an SMB1 Negotiate Request packet' do
@@ -279,33 +722,158 @@ RSpec.describe RubySMB::Client do
279
722
  end
280
723
 
281
724
  it 'sets the default SMB1 Dialect' do
282
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
725
+ expect(client.smb1_negotiate_request.dialects).to include(
726
+ buffer_format: 2,
727
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
728
+ )
283
729
  end
284
730
 
285
731
  it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
286
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
732
+ expect(client.smb1_negotiate_request.dialects).to include(
733
+ buffer_format: 2,
734
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
735
+ )
287
736
  end
288
737
 
289
738
  it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
290
- expect(smb1_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
739
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
740
+ buffer_format: 2,
741
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
742
+ )
291
743
  end
292
744
 
293
745
  it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
294
- expect(smb2_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
746
+ expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
747
+ buffer_format: 2,
748
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
749
+ )
750
+ end
751
+
752
+ it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
753
+ expect(client.smb1_negotiate_request.dialects).to include(
754
+ buffer_format: 2,
755
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
756
+ )
757
+ end
758
+
759
+ it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
760
+ expect(smb3_client.smb1_negotiate_request.dialects).to include(
761
+ buffer_format: 2,
762
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
763
+ )
764
+ end
765
+
766
+ it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
767
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
768
+ buffer_format: 2,
769
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
770
+ )
295
771
  end
296
772
  end
297
773
 
298
- describe '#smb2_negotiate_request' do
774
+ describe '#smb2_3_negotiate_request' do
299
775
  it 'return an SMB2 Negotiate Request packet' do
300
- expect(client.smb2_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
776
+ expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
301
777
  end
302
778
 
303
- it 'sets the default SMB2 Dialect' do
304
- expect(client.smb2_negotiate_request.dialects).to include(RubySMB::Client::SMB2_DIALECT_DEFAULT)
779
+ it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
780
+ expect(client.smb2_3_negotiate_request.dialects).to include(
781
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
782
+ )
783
+ end
784
+
785
+ it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
786
+ expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
787
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
788
+ )
305
789
  end
306
790
 
307
791
  it 'sets the Message ID to 0' do
308
- expect(client.smb2_negotiate_request.smb2_header.message_id).to eq 0
792
+ expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
793
+ end
794
+
795
+ it 'adds SMB3 dialects if if SMB3 support is enabled' do
796
+ expect(client.smb2_3_negotiate_request.dialects).to include(
797
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
798
+ )
799
+ end
800
+
801
+ it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
802
+ expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
803
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
804
+ )
805
+ end
806
+ end
807
+
808
+ describe '#add_smb3_to_negotiate_request' do
809
+ let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
810
+
811
+ it 'adds the default SMB3 dialects' do
812
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
813
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
814
+ )
815
+ end
816
+
817
+ it 'raises the expected exception when the dialects is not an array of strings' do
818
+ dialects = ['0x0300', 0x0302, '0x0311']
819
+ expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
820
+ end
821
+
822
+ it 'sets encryption capability flag' do
823
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
824
+ end
825
+
826
+ context 'when the negotiate packet includes the 0x0311 dialect' do
827
+ before :example do
828
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
829
+ end
830
+
831
+ it 'adds 3 Negotiate Contexts' do
832
+ expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
833
+ end
834
+
835
+ it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
836
+ nc = negotiate_request.negotiate_context_list.select do |n|
837
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
838
+ end
839
+ expect(nc.length).to eq(1)
840
+ expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
841
+ end
842
+
843
+ it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
844
+ nc = negotiate_request.negotiate_context_list.select do |n|
845
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
846
+ end
847
+ expect(nc.length).to eq(1)
848
+ expect(nc.first.data.ciphers).to eq(
849
+ [
850
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
851
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
852
+ ]
853
+ )
854
+ end
855
+
856
+ it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
857
+ nc = negotiate_request.negotiate_context_list.select do |n|
858
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
859
+ end
860
+ expect(nc.length).to eq(1)
861
+ expect(nc.first.data.compression_algorithms).to eq(
862
+ [
863
+ RubySMB::SMB2::CompressionCapabilities::LZNT1,
864
+ RubySMB::SMB2::CompressionCapabilities::LZ77,
865
+ RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
866
+ RubySMB::SMB2::CompressionCapabilities::Pattern_V1
867
+ ]
868
+ )
869
+ end
870
+ end
871
+
872
+ context 'when the negotiate packet does not include the 0x0311 dialect' do
873
+ it 'does not add any Negotiate Context' do
874
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
875
+ expect(negotiate_request.negotiate_context_list?). to be false
876
+ end
309
877
  end
310
878
  end
311
879
 
@@ -320,10 +888,15 @@ RSpec.describe RubySMB::Client do
320
888
  client.negotiate_request
321
889
  end
322
890
 
323
- it 'calls #smb2_negotiate_request if SMB2 is enabled' do
324
- expect(smb2_client).to receive(:smb2_negotiate_request)
891
+ it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
892
+ expect(smb2_client).to receive(:smb2_3_negotiate_request)
325
893
  smb2_client.negotiate_request
326
894
  end
895
+
896
+ it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
897
+ expect(smb3_client).to receive(:smb2_3_negotiate_request)
898
+ smb3_client.negotiate_request
899
+ end
327
900
  end
328
901
 
329
902
  describe '#negotiate_response' do
@@ -332,10 +905,15 @@ RSpec.describe RubySMB::Client do
332
905
  expect(smb1_client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
333
906
  end
334
907
 
335
- it 'raises an exception if the Response is invalid' do
908
+ it 'raises an exception if the response is not a SMB packet' do
336
909
  expect { smb1_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
337
910
  end
338
911
 
912
+ it 'raises an InvalidPacket error if the response is not a valid response' do
913
+ empty_packet.smb_header.command = RubySMB::SMB2::Commands::NEGOTIATE
914
+ expect { smb1_client.negotiate_response(empty_packet.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
915
+ end
916
+
339
917
  it 'considers the response invalid if it is not an actual Negotiate Response' do
340
918
  bogus_response = smb1_extended_response
341
919
  bogus_response.smb_header.command = 0xff
@@ -357,14 +935,36 @@ RSpec.describe RubySMB::Client do
357
935
  it 'raises an exception if the Response is invalid' do
358
936
  expect { smb2_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
359
937
  end
938
+
939
+ it 'considers the response invalid if it is not an actual Negotiate Response' do
940
+ bogus_response = smb2_response
941
+ bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
942
+ expect { smb2_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
943
+ end
360
944
  end
361
945
 
362
- context 'with SMB1 and SMB2 enabled' do
946
+ context 'with only SMB3' do
947
+ it 'returns a properly formed packet' do
948
+ expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
949
+ end
950
+
951
+ it 'raises an exception if the Response is invalid' do
952
+ expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
953
+ end
954
+
955
+ it 'considers the response invalid if it is not an actual Negotiate Response' do
956
+ bogus_response = smb2_response
957
+ bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
958
+ expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
959
+ end
960
+ end
961
+
962
+ context 'with SMB1, SMB2 and SMB3 enabled' do
363
963
  it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
364
964
  expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
365
965
  end
366
966
 
367
- it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
967
+ it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
368
968
  expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
369
969
  end
370
970
  end
@@ -372,9 +972,10 @@ RSpec.describe RubySMB::Client do
372
972
 
373
973
  describe '#parse_negotiate_response' do
374
974
  context 'when SMB1 was Negotiated' do
375
- it 'turns off SMB2 support' do
975
+ it 'turns off SMB2 and SMB3 support' do
376
976
  client.parse_negotiate_response(smb1_extended_response)
377
977
  expect(client.smb2).to be false
978
+ expect(client.smb3).to be false
378
979
  end
379
980
 
380
981
  it 'sets whether or not signing is required' do
@@ -383,67 +984,391 @@ RSpec.describe RubySMB::Client do
383
984
  expect(client.signing_required).to be true
384
985
  end
385
986
 
386
- it 'sets #dialect to the negotiated dialect' do
387
- smb1_extended_response.dialects = [
388
- RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
389
- RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
390
- RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
391
- ]
392
- smb1_extended_response.parameter_block.dialect_index = 1
393
- client.parse_negotiate_response(smb1_extended_response)
394
- expect(client.dialect).to eq 'B'
395
- end
987
+ it 'sets #dialect to the negotiated dialect' do
988
+ smb1_extended_response.dialects = [
989
+ RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
990
+ RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
991
+ RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
992
+ ]
993
+ smb1_extended_response.parameter_block.dialect_index = 1
994
+ client.parse_negotiate_response(smb1_extended_response)
995
+ expect(client.dialect).to eq 'B'
996
+ end
997
+
998
+ it 'returns the string \'SMB1\'' do
999
+ expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
1000
+ end
1001
+
1002
+ it 'sets #negotiated_smb_version to 1' do
1003
+ client.parse_negotiate_response(smb1_extended_response)
1004
+ expect(client.negotiated_smb_version).to eq(1)
1005
+ end
1006
+ end
1007
+
1008
+ context 'when SMB2 was negotiated' do
1009
+ it 'turns off SMB1 and SMB3 support' do
1010
+ client.parse_negotiate_response(smb2_response)
1011
+ expect(client.smb1).to be false
1012
+ expect(client.smb3).to be false
1013
+ end
1014
+
1015
+ it 'sets whether or not signing is required' do
1016
+ smb2_response.security_mode.signing_required = 1
1017
+ client.parse_negotiate_response(smb2_response)
1018
+ expect(client.signing_required).to be true
1019
+ end
1020
+
1021
+ it 'sets #dialect to the negotiated dialect' do
1022
+ smb2_response.dialect_revision = 2
1023
+ client.parse_negotiate_response(smb2_response)
1024
+ expect(client.dialect).to eq '0x0002'
1025
+ end
1026
+
1027
+ it 'returns the string \'SMB2\'' do
1028
+ expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
1029
+ end
1030
+ end
1031
+
1032
+ context 'when SMB3 was negotiated' do
1033
+ it 'turns off SMB1 and SMB2 support' do
1034
+ client.parse_negotiate_response(smb3_response)
1035
+ expect(client.smb1).to be false
1036
+ expect(client.smb2).to be false
1037
+ end
1038
+
1039
+ it 'sets whether or not signing is required' do
1040
+ smb3_response.security_mode.signing_required = 1
1041
+ client.parse_negotiate_response(smb3_response)
1042
+ expect(client.signing_required).to be true
1043
+ end
1044
+
1045
+ it 'sets #dialect to the negotiated dialect' do
1046
+ client.parse_negotiate_response(smb3_response)
1047
+ expect(client.dialect).to eq '0x0300'
1048
+ end
1049
+
1050
+ it 'returns the string \'SMB2\'' do
1051
+ expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
1052
+ end
1053
+ end
1054
+
1055
+ context 'when the response contains the SMB2 wildcard revision number dialect' do
1056
+ it 'only turns off SMB1 support' do
1057
+ smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
1058
+ client.parse_negotiate_response(smb2_response)
1059
+ expect(client.smb1).to be false
1060
+ expect(client.smb2).to be true
1061
+ expect(client.smb3).to be true
1062
+ end
1063
+ end
1064
+
1065
+ context 'when the negotiation failed' do
1066
+ context 'with a STATUS_NOT_SUPPORTED status code' do
1067
+ before :example do
1068
+ error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
1069
+ end
1070
+
1071
+ it 'raises the expected exception with SMB2' do
1072
+ expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
1073
+ RubySMB::Error::NegotiationFailure,
1074
+ 'Unable to negotiate with remote host, SMB2 not supported'
1075
+ )
1076
+ end
1077
+
1078
+ it 'raises the expected exception with SMB3' do
1079
+ expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
1080
+ RubySMB::Error::NegotiationFailure,
1081
+ 'Unable to negotiate with remote host, SMB3 not supported'
1082
+ )
1083
+ end
1084
+ end
1085
+
1086
+ context 'with an unknown status code' do
1087
+ it 'raises the expected exception' do
1088
+ expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
1089
+ RubySMB::Error::NegotiationFailure,
1090
+ 'Unable to negotiate with remote host'
1091
+ )
1092
+ end
1093
+ end
1094
+ end
1095
+ end
1096
+
1097
+ describe '#negotiate' do
1098
+ let(:request_packet) { client.smb1_negotiate_request }
1099
+ before :example do
1100
+ allow(client).to receive(:negotiate_request)
1101
+ allow(client).to receive(:send_recv)
1102
+ allow(client).to receive(:negotiate_response)
1103
+ allow(client).to receive(:parse_negotiate_response)
1104
+ end
1105
+
1106
+ it 'calls the backing methods' do
1107
+ expect(client).to receive(:negotiate_request)
1108
+ expect(client).to receive(:send_recv)
1109
+ expect(client).to receive(:negotiate_response)
1110
+ expect(client).to receive(:parse_negotiate_response)
1111
+ client.negotiate
1112
+ end
1113
+
1114
+ context 'with SMB1' do
1115
+ it 'sets the response-packet #dialects array with the dialects sent in the request' do
1116
+ request_packet = client.smb1_negotiate_request
1117
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1118
+ allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
1119
+ expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
1120
+ client.negotiate
1121
+ end
1122
+ end
1123
+
1124
+ ['0x0300', '0x0302'].each do |dialect|
1125
+ context "with #{dialect} dialect" do
1126
+ before :example do
1127
+ client.dialect = dialect
1128
+ end
1129
+
1130
+ it 'sets the expected encryption algorithm' do
1131
+ client.negotiate
1132
+ expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
1133
+ end
1134
+ end
1135
+ end
1136
+
1137
+ context "with 0x0311 dialect" do
1138
+ it 'calls #parse_smb3_encryption_data' do
1139
+ client.dialect = '0x0311'
1140
+ request_packet = client.smb2_3_negotiate_request
1141
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1142
+ allow(client).to receive(:negotiate_response).and_return(smb3_response)
1143
+ expect(client).to receive(:parse_smb3_encryption_data).with(request_packet, smb3_response)
1144
+ client.negotiate
1145
+ end
1146
+ end
1147
+
1148
+ context 'with a wildcard revision number response' do
1149
+ before :example do
1150
+ client.dialect = '0x02ff'
1151
+ allow(client).to receive(:smb2_message_id=) do
1152
+ client.dialect = '0x0202'
1153
+ end
1154
+ end
1155
+
1156
+ it 'increments the message ID' do
1157
+ expect(client).to receive(:smb2_message_id=).with(1)
1158
+ client.negotiate
1159
+ end
1160
+
1161
+ it 're-negotiates' do
1162
+ expect(client).to receive(:negotiate_request).twice
1163
+ expect(client).to receive(:send_recv).twice
1164
+ expect(client).to receive(:negotiate_response).twice
1165
+ expect(client).to receive(:parse_negotiate_response).twice
1166
+ client.negotiate
1167
+ end
1168
+ end
1169
+
1170
+ context 'when an error occurs' do
1171
+ before :example do
1172
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1173
+ allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
1174
+ client.smb1 = false
1175
+ client.smb2 = false
1176
+ client.smb3 = false
1177
+ end
1178
+
1179
+ context 'with SMB1' do
1180
+ let(:request_packet) { client.smb1_negotiate_request }
1181
+
1182
+ it 'raise the expected exception' do
1183
+ client.smb1 = true
1184
+ expect { client.negotiate }.to raise_error(
1185
+ RubySMB::Error::NegotiationFailure,
1186
+ "Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
1187
+ )
1188
+ end
1189
+ end
1190
+
1191
+ context 'with SMB2' do
1192
+ let(:request_packet) { client.smb2_3_negotiate_request }
1193
+
1194
+ it 'raise the expected exception' do
1195
+ client.smb2 = true
1196
+ expect { client.negotiate }.to raise_error(
1197
+ RubySMB::Error::NegotiationFailure,
1198
+ "Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
1199
+ )
1200
+ end
1201
+ end
1202
+
1203
+ context 'with SMB3' do
1204
+ let(:request_packet) { client.smb2_3_negotiate_request }
396
1205
 
397
- it 'returns the string \'SMB1\'' do
398
- expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
1206
+ it 'raise the expected exception' do
1207
+ client.smb3 = true
1208
+ expect { client.negotiate }.to raise_error(
1209
+ RubySMB::Error::NegotiationFailure,
1210
+ "Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
1211
+ )
1212
+ end
399
1213
  end
400
1214
  end
401
1215
 
402
- context 'when SMB2 was negotiated' do
403
- it 'turns off SMB1 support' do
404
- client.parse_negotiate_response(smb2_response)
405
- expect(client.smb1).to be false
1216
+ describe '#parse_smb3_encryption_data' do
1217
+ let(:request_packet) { client.smb2_3_negotiate_request }
1218
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
1219
+ let(:nc_encryption) do
1220
+ nc = RubySMB::SMB2::NegotiateContext.new(
1221
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1222
+ )
1223
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
1224
+ nc
406
1225
  end
407
-
408
- it 'sets whether or not signing is required' do
409
- smb2_response.security_mode.signing_required = 1
410
- client.parse_negotiate_response(smb2_response)
411
- expect(client.signing_required).to be true
1226
+ let(:nc_integrity) do
1227
+ nc = RubySMB::SMB2::NegotiateContext.new(
1228
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1229
+ )
1230
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
1231
+ nc
412
1232
  end
413
1233
 
414
- it 'sets #dialect to the negotiated dialect' do
415
- smb2_response.dialect_revision = 2
416
- client.parse_negotiate_response(smb2_response)
417
- expect(client.dialect).to eq '0x0002'
1234
+ before :example do
1235
+ allow(smb3_client).to receive(:update_preauth_hash)
1236
+ smb3_response.add_negotiate_context(nc_encryption)
1237
+ smb3_response.add_negotiate_context(nc_integrity)
418
1238
  end
419
1239
 
420
- it 'returns the string \'SMB2\'' do
421
- expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
1240
+ context 'when selecting the integrity hash algorithm' do
1241
+ context 'with one algorithm' do
1242
+ it 'selects the expected algorithm' do
1243
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1244
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1245
+ end
1246
+ end
1247
+
1248
+ context 'with multiple algorithms' do
1249
+ it 'selects the first algorithm' do
1250
+ nc = smb3_response.find_negotiate_context(
1251
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1252
+ )
1253
+ nc.data.hash_algorithms << 3
1254
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1255
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1256
+ end
1257
+ end
1258
+
1259
+ context 'without integrity negotiate context' do
1260
+ it 'raises the expected exception' do
1261
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1262
+ smb3_response.add_negotiate_context(nc_encryption)
1263
+ expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
1264
+ RubySMB::Error::EncryptionError,
1265
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1266
+ )
1267
+ end
1268
+ end
1269
+
1270
+ context 'with an unknown integrity hash algorithm' do
1271
+ it 'raises the expected exception' do
1272
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1273
+ smb3_response.add_negotiate_context(nc_encryption)
1274
+ nc = RubySMB::SMB2::NegotiateContext.new(
1275
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1276
+ )
1277
+ nc.data.hash_algorithms << 5
1278
+ smb3_response.add_negotiate_context(nc)
1279
+ expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
1280
+ RubySMB::Error::EncryptionError,
1281
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1282
+ )
1283
+ end
1284
+ end
422
1285
  end
423
- end
424
- end
425
1286
 
426
- describe '#negotiate' do
427
- it 'calls the backing methods' do
428
- expect(client).to receive(:negotiate_request)
429
- expect(client).to receive(:send_recv)
430
- expect(client).to receive(:negotiate_response)
431
- expect(client).to receive(:parse_negotiate_response)
432
- client.negotiate
433
- end
1287
+ context 'when selecting the encryption algorithm' do
1288
+ context 'with one algorithm' do
1289
+ it 'selects the expected algorithm' do
1290
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1291
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1292
+ end
1293
+ end
434
1294
 
435
- it 'sets the response-packet #dialects array with the dialects sent in the request' do
436
- request_packet = client.smb1_negotiate_request
437
- allow(client).to receive(:negotiate_request).and_return(request_packet)
438
- allow(client).to receive(:send_recv)
439
- allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
440
- expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
441
- client.negotiate
442
- end
1295
+ context 'with multiple algorithms' do
1296
+ it 'selects the AES-128-GCM algorithm if included' do
1297
+ nc = smb3_response.find_negotiate_context(
1298
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1299
+ )
1300
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1301
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1302
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
1303
+ end
1304
+
1305
+ it 'selects the first algorithm if AES-128-GCM is not included' do
1306
+ nc = smb3_response.find_negotiate_context(
1307
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1308
+ )
1309
+ nc.data.ciphers << 3
1310
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1311
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1312
+ end
1313
+
1314
+ it 'keep tracks of the server supported algorithms' do
1315
+ nc = smb3_response.find_negotiate_context(
1316
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1317
+ )
1318
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1319
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1320
+ expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
1321
+ end
1322
+ end
1323
+
1324
+ context 'without encryption context' do
1325
+ it 'raises the expected exception' do
1326
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1327
+ smb3_response.add_negotiate_context(nc_integrity)
1328
+ expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
1329
+ RubySMB::Error::EncryptionError,
1330
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1331
+ )
1332
+ end
1333
+ end
1334
+
1335
+ context 'with an unknown encryption algorithm' do
1336
+ it 'raises the expected exception' do
1337
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1338
+ smb3_response.add_negotiate_context(nc_integrity)
1339
+ nc = RubySMB::SMB2::NegotiateContext.new(
1340
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1341
+ )
1342
+ nc.data.ciphers << 14
1343
+ smb3_response.add_negotiate_context(nc)
1344
+ expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
1345
+ RubySMB::Error::EncryptionError,
1346
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1347
+ )
1348
+ end
1349
+ end
1350
+ end
1351
+
1352
+ context 'when selecting the compression algorithm' do
1353
+ it 'keep tracks of the server supported algorithms' do
1354
+ nc = RubySMB::SMB2::NegotiateContext.new(
1355
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
1356
+ )
1357
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
1358
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
1359
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
1360
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
1361
+ smb3_response.add_negotiate_context(nc)
1362
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1363
+ expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
1364
+ end
1365
+ end
443
1366
 
444
- it 'raise the expected exception if an error occurs' do
445
- allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
446
- expect { client.negotiate }.to raise_error(RubySMB::Error::NegotiationFailure)
1367
+ it 'updates the preauth hash' do
1368
+ expect(smb3_client).to receive(:update_preauth_hash).with(request_packet)
1369
+ expect(smb3_client).to receive(:update_preauth_hash).with(smb3_response)
1370
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1371
+ end
447
1372
  end
448
1373
  end
449
1374
  end
@@ -624,7 +1549,7 @@ RSpec.describe RubySMB::Client do
624
1549
  expect { smb1_client.smb1_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
625
1550
  end
626
1551
 
627
- it 'raises an InvalidPacket if the Command field is wrong' do
1552
+ it 'raise an InvalidPacket exception when the response is not valid' do
628
1553
  expect { smb1_client.smb1_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
629
1554
  end
630
1555
  end
@@ -645,7 +1570,7 @@ RSpec.describe RubySMB::Client do
645
1570
  expect(smb1_client.smb1_ntlmssp_final_packet(response.to_binary_s)).to eq response
646
1571
  end
647
1572
 
648
- it 'raises an InvalidPacket if the Command field is wrong' do
1573
+ it 'raise an InvalidPacket exception when the response is not valid' do
649
1574
  expect { smb1_client.smb1_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
650
1575
  end
651
1576
  end
@@ -717,12 +1642,7 @@ RSpec.describe RubySMB::Client do
717
1642
  expect(smb1_client.smb1_anonymous_auth_response(anonymous_response.to_binary_s)).to eq anonymous_response
718
1643
  end
719
1644
 
720
- it 'returns an empty packet if the raw data is not a valid response' do
721
- empty_packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
722
- expect(smb1_client.smb1_anonymous_auth_response(empty_packet.to_binary_s)).to eq empty_packet
723
- end
724
-
725
- it 'raises an InvalidPacket error if the command is wrong' do
1645
+ it 'raise an InvalidPacket exception when the response is not valid' do
726
1646
  anonymous_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_NEGOTIATE
727
1647
  expect { smb1_client.smb1_anonymous_auth_response(anonymous_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
728
1648
  end
@@ -775,6 +1695,39 @@ RSpec.describe RubySMB::Client do
775
1695
  smb2_client.smb2_authenticate
776
1696
  expect(smb2_client.os_version).to eq '6.1.7601'
777
1697
  end
1698
+
1699
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1700
+ it "does not update the preauth hash with dialect #{dialect}" do
1701
+ smb2_client.dialect = dialect
1702
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1703
+ smb2_client.smb2_authenticate
1704
+ end
1705
+ end
1706
+
1707
+ it "updates the preauth hash with dialect 0x0311" do
1708
+ smb2_client.dialect = '0x0311'
1709
+ expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
1710
+ smb2_client.smb2_authenticate
1711
+ end
1712
+
1713
+ context 'when setting the encryption_required parameter' do
1714
+ before :example do
1715
+ smb2_client.smb3 = true
1716
+ smb2_client.encryption_required = false
1717
+ end
1718
+
1719
+ it 'sets the encryption_required parameter to true if the server requires encryption' do
1720
+ final_response_packet.session_flags.encrypt_data = 1
1721
+ smb2_client.smb2_authenticate
1722
+ expect(smb2_client.encryption_required).to be true
1723
+ end
1724
+
1725
+ it 'does not set the encryption_required parameter if the server does not require encryption' do
1726
+ final_response_packet.session_flags.encrypt_data = 0
1727
+ smb2_client.smb2_authenticate
1728
+ expect(smb2_client.encryption_required).to be false
1729
+ end
1730
+ end
778
1731
  end
779
1732
 
780
1733
  describe '#smb2_ntlmssp_negotiate_packet' do
@@ -790,20 +1743,34 @@ RSpec.describe RubySMB::Client do
790
1743
  smb2_client.smb2_ntlmssp_negotiate_packet
791
1744
  end
792
1745
 
793
- it 'sets the message ID in the packet header to 1' do
794
- expect(smb2_client.smb2_ntlmssp_negotiate_packet.smb2_header.message_id).to eq 1
795
- end
796
-
797
- it 'increments client#smb2_message_id' do
798
- expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
1746
+ it 'enables signing' do
1747
+ expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
799
1748
  end
800
1749
  end
801
1750
 
802
1751
  describe '#smb2_ntlmssp_negotiate' do
1752
+ before :example do
1753
+ allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
1754
+ allow(smb2_client).to receive(:send_recv)
1755
+ end
1756
+
803
1757
  it 'sends the request packet and receives a response' do
804
- expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
805
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
806
- expect(dispatcher).to receive(:recv_packet)
1758
+ expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
1759
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1760
+ smb2_client.smb2_ntlmssp_negotiate
1761
+ end
1762
+
1763
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1764
+ it "does not update the preauth hash with dialect #{dialect}" do
1765
+ smb2_client.dialect = dialect
1766
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1767
+ smb2_client.smb2_ntlmssp_negotiate
1768
+ end
1769
+ end
1770
+
1771
+ it "updates the preauth hash with dialect 0x0311" do
1772
+ smb2_client.dialect = '0x0311'
1773
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
807
1774
  smb2_client.smb2_ntlmssp_negotiate
808
1775
  end
809
1776
  end
@@ -829,7 +1796,7 @@ RSpec.describe RubySMB::Client do
829
1796
  expect { smb2_client.smb2_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
830
1797
  end
831
1798
 
832
- it 'raises an InvalidPacket if the Command field is wrong' do
1799
+ it 'raise an InvalidPacket exception when the response is not valid' do
833
1800
  expect { smb2_client.smb2_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
834
1801
  end
835
1802
  end
@@ -861,13 +1828,35 @@ RSpec.describe RubySMB::Client do
861
1828
  it 'sets the session ID on the request packet' do
862
1829
  expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
863
1830
  end
1831
+
1832
+ it 'enables signing' do
1833
+ expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
1834
+ end
864
1835
  end
865
1836
 
866
1837
  describe '#smb2_ntlmssp_authenticate' do
1838
+ before :example do
1839
+ allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
1840
+ allow(smb2_client).to receive(:send_recv)
1841
+ end
1842
+
867
1843
  it 'sends the request packet and receives a response' do
868
- expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
869
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
870
- expect(dispatcher).to receive(:recv_packet)
1844
+ expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
1845
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1846
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1847
+ end
1848
+
1849
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1850
+ it "does not update the preauth hash with dialect #{dialect}" do
1851
+ smb2_client.dialect = dialect
1852
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1853
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1854
+ end
1855
+ end
1856
+
1857
+ it "updates the preauth hash with dialect 0x0311" do
1858
+ smb2_client.dialect = '0x0311'
1859
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
871
1860
  smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
872
1861
  end
873
1862
  end
@@ -888,7 +1877,7 @@ RSpec.describe RubySMB::Client do
888
1877
  expect(smb2_client.smb2_ntlmssp_final_packet(response.to_binary_s)).to eq response
889
1878
  end
890
1879
 
891
- it 'raises an InvalidPacket if the Command field is wrong' do
1880
+ it 'raise an InvalidPacket exception when the response is not valid' do
892
1881
  expect { smb2_client.smb2_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
893
1882
  end
894
1883
  end
@@ -1003,6 +1992,108 @@ RSpec.describe RubySMB::Client do
1003
1992
  end
1004
1993
  end
1005
1994
  end
1995
+
1996
+ describe '#smb3_sign' do
1997
+ context 'if signing is required and we have a session key' do
1998
+ let(:request) {
1999
+ packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
2000
+ packet.smb2_header.flags.signed = 1
2001
+ packet.smb2_header.signature = "\x00" * 16
2002
+ packet
2003
+ }
2004
+ let(:session_key) { 'Session Key' }
2005
+ before :example do
2006
+ smb3_client.session_key = session_key
2007
+ smb3_client.signing_required = true
2008
+ end
2009
+
2010
+ ['0x0300', '0x0302'].each do |dialect|
2011
+ context "with #{dialect} dialect" do
2012
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2013
+ smb3_client.dialect = dialect
2014
+ fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
2015
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2016
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2017
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2018
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2019
+ end
2020
+ end
2021
+ end
2022
+
2023
+ context "with 0x0311 dialect" do
2024
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2025
+ smb3_client.dialect = '0x0311'
2026
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2027
+ fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
2028
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2029
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2030
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2031
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2032
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2033
+ end
2034
+ end
2035
+
2036
+ context 'with an incompatible dialect' do
2037
+ it 'raises the expected exception' do
2038
+ smb3_client.dialect = '0x0202'
2039
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2040
+ RubySMB::Error::SigningError,
2041
+ 'Dialect is incompatible with SMBv3 signing'
2042
+ )
2043
+ end
2044
+ end
2045
+ end
2046
+
2047
+ context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
2048
+ let(:request) {
2049
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
2050
+ packet.smb2_header.flags.signed = 1
2051
+ packet.smb2_header.signature = "\x00" * 16
2052
+ packet
2053
+ }
2054
+ let(:session_key) { 'Session Key' }
2055
+ before :example do
2056
+ smb3_client.session_key = session_key
2057
+ smb3_client.signing_required = false
2058
+ end
2059
+
2060
+ ['0x0300', '0x0302'].each do |dialect|
2061
+ context "with #{dialect} dialect" do
2062
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2063
+ smb3_client.dialect = dialect
2064
+ fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
2065
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2066
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2067
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2068
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2069
+ end
2070
+ end
2071
+ end
2072
+
2073
+ context "with 0x0311 dialect" do
2074
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2075
+ smb3_client.dialect = '0x0311'
2076
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2077
+ fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
2078
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2079
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2080
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2081
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2082
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2083
+ end
2084
+ end
2085
+
2086
+ context 'with an incompatible dialect' do
2087
+ it 'raises the expected exception' do
2088
+ smb3_client.dialect = '0x0202'
2089
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2090
+ RubySMB::Error::SigningError,
2091
+ 'Dialect is incompatible with SMBv3 signing'
2092
+ )
2093
+ end
2094
+ end
2095
+ end
2096
+ end
1006
2097
  end
1007
2098
 
1008
2099
  context '#increment_smb_message_id' do
@@ -1056,7 +2147,10 @@ RSpec.describe RubySMB::Client do
1056
2147
 
1057
2148
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1058
2149
  response.smb_header.nt_status = 0xc0000015
1059
- expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
2150
+ expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
2151
+ RubySMB::Error::UnexpectedStatusCode,
2152
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2153
+ )
1060
2154
  end
1061
2155
 
1062
2156
  it 'creates a new Tree from itself, the share path, and the response packet' do
@@ -1077,11 +2171,14 @@ RSpec.describe RubySMB::Client do
1077
2171
  }
1078
2172
 
1079
2173
  describe '#smb2_tree_connect' do
1080
- it 'builds and sends a TreeconnectRequest for the supplied share' do
2174
+ it 'builds and sends the expected TreeconnectRequest for the supplied share' do
1081
2175
  allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
1082
- modified_request = request
1083
- modified_request.encode_path(path)
1084
- expect(smb2_client).to receive(:send_recv).with(modified_request).and_return(response.to_binary_s)
2176
+ expect(smb2_client).to receive(:send_recv) do |req|
2177
+ expect(req).to eq(request)
2178
+ expect(req.smb2_header.tree_id).to eq(65_535)
2179
+ expect(req.path).to eq(path.encode('UTF-16LE'))
2180
+ response.to_binary_s
2181
+ end
1085
2182
  smb2_client.smb2_tree_connect(path)
1086
2183
  end
1087
2184
 
@@ -1100,11 +2197,20 @@ RSpec.describe RubySMB::Client do
1100
2197
 
1101
2198
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1102
2199
  response.smb2_header.nt_status = 0xc0000015
1103
- expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
2200
+ expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
2201
+ RubySMB::Error::UnexpectedStatusCode,
2202
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2203
+ )
1104
2204
  end
1105
2205
 
1106
2206
  it 'creates a new Tree from itself, the share path, and the response packet' do
1107
- expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
2207
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
2208
+ smb2_client.smb2_tree_from_response(path, response)
2209
+ end
2210
+
2211
+ it 'creates a new with encryption set if the response requires it' do
2212
+ response.share_flags.encrypt = 1
2213
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
1108
2214
  smb2_client.smb2_tree_from_response(path, response)
1109
2215
  end
1110
2216
  end
@@ -1199,6 +2305,12 @@ RSpec.describe RubySMB::Client do
1199
2305
  expect(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
1200
2306
  expect(smb1_client.echo).to eq WindowsError::NTStatus::STATUS_ABANDONED
1201
2307
  end
2308
+
2309
+ it 'raise an InvalidPacket exception when the response is not valid' do
2310
+ echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
2311
+ allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
2312
+ expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
2313
+ end
1202
2314
  end
1203
2315
 
1204
2316
  context 'with SMB2' do
@@ -1210,8 +2322,355 @@ RSpec.describe RubySMB::Client do
1210
2322
  expect(smb2_client).to receive(:send_recv).with(echo_request).and_return(echo_response.to_binary_s)
1211
2323
  expect(smb2_client.smb2_echo).to eq echo_response
1212
2324
  end
2325
+
2326
+ it 'raise an InvalidPacket exception when the response is not valid' do
2327
+ echo_response.smb2_header.command = RubySMB::SMB2::Commands::SESSION_SETUP
2328
+ allow(smb2_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
2329
+ expect { smb2_client.smb2_echo }.to raise_error(RubySMB::Error::InvalidPacket)
2330
+ end
2331
+ end
2332
+ end
2333
+
2334
+ context 'Winreg' do
2335
+ describe '#connect_to_winreg' do
2336
+ let(:host) { '1.2.3.4' }
2337
+ let(:share) { "\\\\#{host}\\IPC$" }
2338
+ let(:ipc_tree) { double('IPC$ tree') }
2339
+ let(:named_pipe) { double('Named pipe') }
2340
+ before :example do
2341
+ allow(ipc_tree).to receive_messages(
2342
+ :share => share,
2343
+ :open_file => named_pipe
2344
+ )
2345
+ allow(client).to receive(:tree_connect).and_return(ipc_tree)
2346
+ end
2347
+
2348
+ context 'when the client is already connected to the IPC$ share' do
2349
+ before :example do
2350
+ client.tree_connects << ipc_tree
2351
+ allow(ipc_tree).to receive(:share).and_return(share)
2352
+ end
2353
+
2354
+ it 'does not connect to the already connected tree' do
2355
+ client.connect_to_winreg(host)
2356
+ expect(client).to_not have_received(:tree_connect)
2357
+ end
2358
+ end
2359
+
2360
+ it 'calls #tree_connect' do
2361
+ client.connect_to_winreg(host)
2362
+ expect(client).to have_received(:tree_connect).with(share)
2363
+ end
2364
+
2365
+ it 'open \'winreg\' file on the IPC$ Tree' do
2366
+ client.connect_to_winreg(host)
2367
+ expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
2368
+ end
2369
+
2370
+ it 'returns the expected opened named pipe' do
2371
+ expect(client.connect_to_winreg(host)).to eq(named_pipe)
2372
+ end
2373
+
2374
+ context 'when a block is given' do
2375
+ before :example do
2376
+ allow(named_pipe).to receive(:close)
2377
+ end
2378
+
2379
+ it 'yields the expected named_pipe' do
2380
+ client.connect_to_winreg(host) do |np|
2381
+ expect(np).to eq(named_pipe)
2382
+ end
2383
+ end
2384
+
2385
+ it 'closes the named pipe' do
2386
+ client.connect_to_winreg(host) { |np| }
2387
+ expect(named_pipe).to have_received(:close)
2388
+ end
2389
+
2390
+ it 'returns the block return value' do
2391
+ result = double('Result')
2392
+ expect(client.connect_to_winreg(host) { |np| result }).to eq(result)
2393
+ end
2394
+ end
2395
+ end
2396
+
2397
+ describe '#has_registry_key?' do
2398
+ let(:host) { '1.2.3.4' }
2399
+ let(:key) { 'HKLM\\Registry\\Key' }
2400
+ let(:named_pipe) { double('Named pipe') }
2401
+ let(:result) { double('Result') }
2402
+ before :example do
2403
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2404
+ allow(named_pipe).to receive(:has_registry_key?).and_return(result)
2405
+ end
2406
+
2407
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2408
+ client.has_registry_key?(host, key)
2409
+ expect(client).to have_received(:connect_to_winreg).with(host)
2410
+ end
2411
+
2412
+ it 'calls Pipe #has_registry_key?' do
2413
+ client.has_registry_key?(host, key)
2414
+ expect(named_pipe).to have_received(:has_registry_key?).with(key)
2415
+ end
2416
+ end
2417
+
2418
+ describe '#read_registry_key_value' do
2419
+ let(:host) { '1.2.3.4' }
2420
+ let(:key) { 'HKLM\\Registry\\Key' }
2421
+ let(:value_name) { 'Value' }
2422
+ let(:named_pipe) { double('Named pipe') }
2423
+ let(:result) { double('Result') }
2424
+ before :example do
2425
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2426
+ allow(named_pipe).to receive(:read_registry_key_value).and_return(result)
2427
+ end
2428
+
2429
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2430
+ client.read_registry_key_value(host, key, value_name)
2431
+ expect(client).to have_received(:connect_to_winreg).with(host)
2432
+ end
2433
+
2434
+ it 'calls Pipe #read_registry_key_value' do
2435
+ client.read_registry_key_value(host, key, value_name)
2436
+ expect(named_pipe).to have_received(:read_registry_key_value).with(key, value_name)
2437
+ end
2438
+ end
2439
+
2440
+ describe '#enum_registry_key' do
2441
+ let(:host) { '1.2.3.4' }
2442
+ let(:key) { 'HKLM\\Registry\\Key' }
2443
+ let(:named_pipe) { double('Named pipe') }
2444
+ let(:result) { double('Result') }
2445
+ before :example do
2446
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2447
+ allow(named_pipe).to receive(:enum_registry_key).and_return(result)
2448
+ end
2449
+
2450
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2451
+ client.enum_registry_key(host, key)
2452
+ expect(client).to have_received(:connect_to_winreg).with(host)
2453
+ end
2454
+
2455
+ it 'calls Pipe #enum_registry_key' do
2456
+ client.enum_registry_key(host, key)
2457
+ expect(named_pipe).to have_received(:enum_registry_key).with(key)
2458
+ end
2459
+ end
2460
+
2461
+ describe '#enum_registry_values' do
2462
+ let(:host) { '1.2.3.4' }
2463
+ let(:key) { 'HKLM\\Registry\\Key' }
2464
+ let(:named_pipe) { double('Named pipe') }
2465
+ let(:result) { double('Result') }
2466
+ before :example do
2467
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2468
+ allow(named_pipe).to receive(:enum_registry_values).and_return(result)
2469
+ end
2470
+
2471
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2472
+ client.enum_registry_values(host, key)
2473
+ expect(client).to have_received(:connect_to_winreg).with(host)
2474
+ end
2475
+
2476
+ it 'calls Pipe #enum_registry_values' do
2477
+ client.enum_registry_values(host, key)
2478
+ expect(named_pipe).to have_received(:enum_registry_values).with(key)
2479
+ end
1213
2480
  end
1214
2481
  end
1215
2482
 
2483
+ describe '#update_preauth_hash' do
2484
+ it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
2485
+ expect { client.update_preauth_hash('Test') }.to raise_error(
2486
+ RubySMB::Error::EncryptionError,
2487
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
2488
+ )
2489
+ end
2490
+
2491
+ it 'computes the hash value' do
2492
+ packet = RubySMB::SMB2::Packet::EchoRequest.new
2493
+ data = 'Previous hash'
2494
+ algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
2495
+ RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
2496
+ ]
2497
+ client.preauth_integrity_hash_algorithm = algo
2498
+ client.preauth_integrity_hash_value = data
2499
+ hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
2500
+ client.update_preauth_hash(packet)
2501
+ expect(client.preauth_integrity_hash_value).to eq(hash)
2502
+ end
2503
+ end
2504
+
2505
+ context 'Encryption' do
2506
+ describe '#smb3_encrypt' do
2507
+ let(:transform_packet) { double('TransformHeader packet') }
2508
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2509
+ let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
2510
+
2511
+ before :example do
2512
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
2513
+ allow(transform_packet).to receive(:encrypt)
2514
+ client.session_key = session_key
2515
+ end
2516
+
2517
+ it 'does not generate a new client encryption key if it already exists' do
2518
+ client.client_encryption_key = 'key'
2519
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2520
+ expect(client.client_encryption_key).to eq('key')
2521
+ client.smb3_encrypt(data)
2522
+ end
2523
+
2524
+ ['0x0300', '0x0302'].each do |dialect|
2525
+ context "with #{dialect} dialect" do
2526
+ before :example do
2527
+ client.dialect = dialect
2528
+ end
2529
+
2530
+ it 'generates the client encryption key with the expected parameters' do
2531
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2532
+ session_key,
2533
+ "SMB2AESCCM\x00",
2534
+ "ServerIn \x00"
2535
+ ).and_call_original
2536
+ client.smb3_encrypt(data)
2537
+ end
2538
+ end
2539
+ end
2540
+
2541
+ context 'with 0x0311 dialect' do
2542
+ it 'generates the client encryption key with the expected parameters' do
2543
+ client.preauth_integrity_hash_value = ''
2544
+ client.dialect = '0x0311'
2545
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2546
+ session_key,
2547
+ "SMBC2SCipherKey\x00",
2548
+ ''
2549
+ ).and_call_original
2550
+ client.smb3_encrypt(data)
2551
+ end
2552
+ end
2553
+
2554
+ it 'raises the expected exception if the dialect is incompatible' do
2555
+ client.dialect = '0x0202'
2556
+ expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
2557
+ end
2558
+
2559
+ it 'creates a TransformHeader packet and encrypt the data' do
2560
+ client.dialect = '0x0300'
2561
+ client.encryption_algorithm = 'AES-128-CCM'
2562
+ client.session_id = 123
2563
+ client.smb3_encrypt(data)
2564
+ expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
2565
+ expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
2566
+ end
2567
+
2568
+ it 'generates the expected client encryption key with 0x0302 dialect' do
2569
+ client.dialect = '0x0302'
2570
+ expected_enc_key =
2571
+ "\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
2572
+ client.smb3_encrypt(data)
2573
+ expect(client.client_encryption_key).to eq expected_enc_key
2574
+ end
2575
+
2576
+ it 'generates the expected client encryption key with 0x0311 dialect' do
2577
+ client.dialect = '0x0311'
2578
+ client.session_key =
2579
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2580
+ client.preauth_integrity_hash_value =
2581
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2582
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2583
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2584
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2585
+ expected_enc_key =
2586
+ "\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
2587
+ client.smb3_encrypt(data)
2588
+ expect(client.client_encryption_key).to eq expected_enc_key
2589
+ end
2590
+ end
2591
+
2592
+ describe '#smb3_decrypt' do
2593
+ let(:transform_packet) { double('TransformHeader packet') }
2594
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2595
+
2596
+ before :example do
2597
+ allow(transform_packet).to receive(:decrypt)
2598
+ client.session_key = session_key
2599
+ end
2600
+
2601
+ it 'does not generate a new server encryption key if it already exists' do
2602
+ client.server_encryption_key = 'key'
2603
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2604
+ expect(client.server_encryption_key).to eq('key')
2605
+ client.smb3_decrypt(transform_packet)
2606
+ end
2607
+
2608
+ ['0x0300', '0x0302'].each do |dialect|
2609
+ context "with #{dialect} dialect" do
2610
+ before :example do
2611
+ client.dialect = dialect
2612
+ end
2613
+
2614
+ it 'generates the client encryption key with the expected parameters' do
2615
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2616
+ session_key,
2617
+ "SMB2AESCCM\x00",
2618
+ "ServerOut\x00"
2619
+ ).and_call_original
2620
+ client.smb3_decrypt(transform_packet)
2621
+ end
2622
+ end
2623
+ end
2624
+
2625
+ context 'with 0x0311 dialect' do
2626
+ it 'generates the client encryption key with the expected parameters' do
2627
+ client.preauth_integrity_hash_value = ''
2628
+ client.dialect = '0x0311'
2629
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2630
+ session_key,
2631
+ "SMBS2CCipherKey\x00",
2632
+ ''
2633
+ ).and_call_original
2634
+ client.smb3_decrypt(transform_packet)
2635
+ end
2636
+ end
2637
+
2638
+ it 'raises the expected exception if the dialect is incompatible' do
2639
+ client.dialect = '0x0202'
2640
+ expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
2641
+ end
2642
+
2643
+ it 'creates a TransformHeader packet and encrypt the data' do
2644
+ client.dialect = '0x0300'
2645
+ client.encryption_algorithm = 'AES-128-CCM'
2646
+ client.session_id = 123
2647
+ client.smb3_decrypt(transform_packet)
2648
+ expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
2649
+ end
2650
+
2651
+ it 'generates the expected server encryption key with 0x0302 dialect' do
2652
+ client.dialect = '0x0302'
2653
+ expected_enc_key =
2654
+ "\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
2655
+ client.smb3_decrypt(transform_packet)
2656
+ expect(client.server_encryption_key).to eq expected_enc_key
2657
+ end
2658
+
2659
+ it 'generates the expected server encryption key with 0x0311 dialect' do
2660
+ client.dialect = '0x0311'
2661
+ client.session_key =
2662
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2663
+ client.preauth_integrity_hash_value =
2664
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2665
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2666
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2667
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2668
+ expected_enc_key =
2669
+ "\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
2670
+ client.smb3_decrypt(transform_packet)
2671
+ expect(client.server_encryption_key).to eq expected_enc_key
2672
+ end
2673
+ end
2674
+ end
1216
2675
  end
1217
2676