ruby_smb 1.1.0 → 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 (65) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -4
  4. data/.travis.yml +3 -5
  5. data/Gemfile +6 -2
  6. data/examples/negotiate.rb +51 -8
  7. data/examples/read_file_encryption.rb +56 -0
  8. data/lib/ruby_smb.rb +4 -0
  9. data/lib/ruby_smb/client.rb +172 -16
  10. data/lib/ruby_smb/client/authentication.rb +27 -8
  11. data/lib/ruby_smb/client/encryption.rb +62 -0
  12. data/lib/ruby_smb/client/negotiation.rb +133 -12
  13. data/lib/ruby_smb/client/signing.rb +19 -0
  14. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  15. data/lib/ruby_smb/client/utils.rb +8 -7
  16. data/lib/ruby_smb/crypto.rb +30 -0
  17. data/lib/ruby_smb/dispatcher/socket.rb +2 -2
  18. data/lib/ruby_smb/error.rb +28 -1
  19. data/lib/ruby_smb/smb1/commands.rb +1 -1
  20. data/lib/ruby_smb/smb1/file.rb +4 -4
  21. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  22. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  23. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  24. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  25. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  26. data/lib/ruby_smb/smb1/pipe.rb +2 -2
  27. data/lib/ruby_smb/smb1/tree.rb +3 -3
  28. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  29. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  30. data/lib/ruby_smb/smb2/file.rb +25 -43
  31. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  32. data/lib/ruby_smb/smb2/packet.rb +2 -0
  33. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  34. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  35. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +49 -3
  36. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  37. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  38. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  39. data/lib/ruby_smb/smb2/pipe.rb +3 -16
  40. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  41. data/lib/ruby_smb/smb2/tree.rb +23 -17
  42. data/lib/ruby_smb/version.rb +1 -1
  43. data/ruby_smb.gemspec +3 -1
  44. data/spec/lib/ruby_smb/client_spec.rb +1256 -57
  45. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  46. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  47. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  48. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  49. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  50. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  51. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  52. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  53. data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
  54. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  55. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  56. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  57. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  58. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  59. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  60. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  61. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +0 -40
  62. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  63. data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
  64. metadata +124 -75
  65. metadata.gz.sig +0 -0
@@ -46,7 +46,7 @@ module RubySMB
46
46
  end
47
47
 
48
48
  unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
49
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
49
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
50
50
  end
51
51
  response
52
52
  end
@@ -101,22 +101,9 @@ module RubySMB
101
101
  received_cmd: ioctl_response.smb2_header.command
102
102
  )
103
103
  end
104
- # TODO: improve the handling of STATUS_PENDING responses
105
- if ioctl_response.status_code == WindowsError::NTStatus::STATUS_PENDING
106
- sleep 1
107
- ioctl_raw_response = @tree.client.dispatcher.recv_packet
108
- ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
109
- unless ioctl_response.valid?
110
- raise RubySMB::Error::InvalidPacket.new(
111
- expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
112
- expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
113
- received_proto: ioctl_response.smb2_header.protocol,
114
- received_cmd: ioctl_response.smb2_header.command
115
- )
116
- end
117
- elsif ![WindowsError::NTStatus::STATUS_SUCCESS,
104
+ unless [WindowsError::NTStatus::STATUS_SUCCESS,
118
105
  WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
119
- raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code.name
106
+ raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
120
107
  end
121
108
 
122
109
  raw_data = ioctl_response.output_data
@@ -6,7 +6,7 @@ module RubySMB
6
6
  endian :little
7
7
  bit32 :protocol, label: 'Protocol ID Field', initial_value: RubySMB::SMB2::SMB2_PROTOCOL_ID
8
8
  uint16 :structure_size, label: 'Header Structure Size', initial_value: 64
9
- uint16 :credit_charge, label: 'Credit Charge', initial_value: 0
9
+ uint16 :credit_charge, label: 'Credit Charge', initial_value: 1
10
10
  nt_status :nt_status, label: 'NT Status', initial_value: 0
11
11
  uint16 :command, label: 'Command'
12
12
  uint16 :credits, label: 'Credit Request/Response'
@@ -23,12 +23,18 @@ module RubySMB
23
23
  # @return [Integer]
24
24
  attr_accessor :id
25
25
 
26
- def initialize(client:, share:, response:)
27
- @client = client
28
- @share = share
29
- @id = response.smb2_header.tree_id
30
- @permissions = response.maximal_access
31
- @share_type = response.share_type
26
+ # Whether or not 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
@@ -38,7 +44,7 @@ module RubySMB
38
44
  def disconnect!
39
45
  request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
40
46
  request = set_header_fields(request)
41
- raw_response = client.send_recv(request)
47
+ raw_response = client.send_recv(request, encrypt: @encryption_required)
42
48
  response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
43
49
  unless response.valid?
44
50
  raise RubySMB::Error::InvalidPacket.new(
@@ -96,7 +102,7 @@ module RubySMB
96
102
  create_request.create_disposition = disposition
97
103
  create_request.name = filename
98
104
 
99
- raw_response = client.send_recv(create_request)
105
+ raw_response = client.send_recv(create_request, encrypt: @encryption_required)
100
106
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
101
107
  unless response.valid?
102
108
  raise RubySMB::Error::InvalidPacket.new(
@@ -107,15 +113,15 @@ module RubySMB
107
113
  )
108
114
  end
109
115
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
110
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
116
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
111
117
  end
112
118
 
113
119
  case @share_type
114
- when 0x01
115
- RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
116
- when 0x02
120
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
121
+ RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @encryption_required)
122
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
117
123
  RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
118
- # when 0x03
124
+ # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
119
125
  # it's a printer!
120
126
  else
121
127
  raise RubySMB::Error::RubySMBError, 'Unsupported share type'
@@ -148,7 +154,7 @@ module RubySMB
148
154
  files = []
149
155
 
150
156
  loop do
151
- response = client.send_recv(directory_request)
157
+ response = client.send_recv(directory_request, encrypt: @encryption_required)
152
158
  directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
153
159
  unless directory_response.valid?
154
160
  raise RubySMB::Error::InvalidPacket.new(
@@ -164,7 +170,7 @@ module RubySMB
164
170
  break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
165
171
 
166
172
  unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
167
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
173
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
168
174
  end
169
175
 
170
176
  files += directory_response.results(type)
@@ -193,7 +199,7 @@ module RubySMB
193
199
 
194
200
  create_request = open_directory_packet(directory: directory, disposition: disposition,
195
201
  impersonation: impersonation, read: read, write: write, delete: delete)
196
- raw_response = client.send_recv(create_request)
202
+ raw_response = client.send_recv(create_request, encrypt: @encryption_required)
197
203
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
198
204
  unless response.valid?
199
205
  raise RubySMB::Error::InvalidPacket.new(
@@ -204,7 +210,7 @@ module RubySMB
204
210
  )
205
211
  end
206
212
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
207
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
213
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
208
214
  end
209
215
 
210
216
  response
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.platform = Gem::Platform::RUBY
27
27
  end
28
28
 
29
- spec.required_ruby_version = '>= 2.3.0'
29
+ spec.required_ruby_version = '>= 2.5'
30
30
 
31
31
  spec.add_development_dependency 'bundler'
32
32
  spec.add_development_dependency 'fivemat'
@@ -36,4 +36,6 @@ Gem::Specification.new do |spec|
36
36
  spec.add_runtime_dependency 'rubyntlm'
37
37
  spec.add_runtime_dependency 'windows_error'
38
38
  spec.add_runtime_dependency 'bindata'
39
+ spec.add_runtime_dependency 'openssl-ccm'
40
+ spec.add_runtime_dependency 'openssl-cmac'
39
41
  end
@@ -6,11 +6,66 @@ RSpec.describe RubySMB::Client do
6
6
  let(:username) { 'msfadmin' }
7
7
  let(:password) { 'msfpasswd' }
8
8
  subject(:client) { described_class.new(dispatcher, username: username, password: password) }
9
- let(:smb1_client) { described_class.new(dispatcher, smb2: false, username: username, password: password) }
10
- let(:smb2_client) { described_class.new(dispatcher, smb1: false, username: username, password: password) }
9
+ let(:smb1_client) { described_class.new(dispatcher, smb2: false, smb3: false, username: username, password: password) }
10
+ let(:smb2_client) { described_class.new(dispatcher, smb1: false, smb3: false, username: username, password: password) }
11
+ let(:smb3_client) { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }
11
12
  let(:empty_packet) { RubySMB::SMB1::Packet::EmptyPacket.new }
12
13
  let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
13
14
 
15
+ it { is_expected.to respond_to :dispatcher }
16
+ it { is_expected.to respond_to :domain }
17
+ it { is_expected.to respond_to :local_workstation }
18
+ it { is_expected.to respond_to :ntlm_client }
19
+ it { is_expected.to respond_to :password }
20
+ it { is_expected.to respond_to :peer_native_os }
21
+ it { is_expected.to respond_to :peer_native_lm }
22
+ it { is_expected.to respond_to :primary_domain }
23
+ it { is_expected.to respond_to :default_name }
24
+ it { is_expected.to respond_to :default_domain }
25
+ it { is_expected.to respond_to :dns_host_name }
26
+ it { is_expected.to respond_to :dns_domain_name }
27
+ it { is_expected.to respond_to :dns_tree_name }
28
+ it { is_expected.to respond_to :os_version }
29
+ it { is_expected.to respond_to :dialect }
30
+ it { is_expected.to respond_to :sequence_counter }
31
+ it { is_expected.to respond_to :session_id }
32
+ it { is_expected.to respond_to :signing_required }
33
+ it { is_expected.to respond_to :smb1 }
34
+ it { is_expected.to respond_to :smb2 }
35
+ it { is_expected.to respond_to :smb3 }
36
+ it { is_expected.to respond_to :smb2_message_id }
37
+ it { is_expected.to respond_to :username }
38
+ it { is_expected.to respond_to :user_id }
39
+ it { is_expected.to respond_to :max_buffer_size }
40
+ it { is_expected.to respond_to :server_max_buffer_size }
41
+ it { is_expected.to respond_to :server_max_write_size }
42
+ it { is_expected.to respond_to :server_max_read_size }
43
+ it { is_expected.to respond_to :server_max_transact_size }
44
+ it { is_expected.to respond_to :preauth_integrity_hash_algorithm }
45
+ it { is_expected.to respond_to :preauth_integrity_hash_value }
46
+ it { is_expected.to respond_to :encryption_algorithm }
47
+ it { is_expected.to respond_to :client_encryption_key }
48
+ it { is_expected.to respond_to :server_encryption_key }
49
+ it { is_expected.to respond_to :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 }
68
+
14
69
  describe '#initialize' do
15
70
  it 'should raise an ArgumentError without a valid dispatcher' do
16
71
  expect { described_class.new(nil) }.to raise_error(ArgumentError)
@@ -26,14 +81,21 @@ RSpec.describe RubySMB::Client do
26
81
 
27
82
  it 'accepts an argument to disable smb1 support' do
28
83
  expect(smb2_client.smb1).to be false
84
+ expect(smb3_client.smb1).to be false
29
85
  end
30
86
 
31
87
  it 'accepts an argument to disable smb2 support' do
32
88
  expect(smb1_client.smb2).to be false
89
+ expect(smb3_client.smb2).to be false
90
+ end
91
+
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
33
95
  end
34
96
 
35
- it 'raises an exception if both SMB1 and SMB2 are disabled' do
36
- expect { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
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')
37
99
  end
38
100
 
39
101
  it 'sets the username attribute' do
@@ -44,6 +106,11 @@ RSpec.describe RubySMB::Client do
44
106
  expect(client.password).to eq password
45
107
  end
46
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
+
47
114
  it 'creates an NTLM client' do
48
115
  expect(client.ntlm_client).to be_a Net::NTLM::Client
49
116
  end
@@ -76,13 +143,52 @@ RSpec.describe RubySMB::Client do
76
143
  end
77
144
  end
78
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
+
79
184
  describe '#send_recv' do
80
185
  let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
81
186
  let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
82
187
 
83
188
  before(:each) do
84
- expect(dispatcher).to receive(:send_packet).and_return(nil)
85
- 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')
86
192
  end
87
193
 
88
194
  it 'checks the packet version' do
@@ -90,14 +196,218 @@ RSpec.describe RubySMB::Client do
90
196
  client.send_recv(smb1_request)
91
197
  end
92
198
 
93
- it 'calls #smb1_sign if it is an SMB1 packet' do
94
- expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
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)
95
229
  client.send_recv(smb1_request)
96
230
  end
97
231
 
98
- it 'calls #smb2_sign if it is an SMB2 packet' do
99
- expect(client).to receive(:smb2_sign).with(smb2_request).and_call_original
100
- client.send_recv(smb2_request)
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
+ )
101
411
  end
102
412
  end
103
413
 
@@ -240,6 +550,44 @@ RSpec.describe RubySMB::Client do
240
550
  expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
241
551
  end
242
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
243
591
  end
244
592
 
245
593
  context 'NetBIOS Session Service' do
@@ -365,7 +713,8 @@ RSpec.describe RubySMB::Client do
365
713
  smb1_extended_response.to_binary_s
366
714
  }
367
715
 
368
- 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) }
369
718
 
370
719
  describe '#smb1_negotiate_request' do
371
720
  it 'returns an SMB1 Negotiate Request packet' do
@@ -373,33 +722,158 @@ RSpec.describe RubySMB::Client do
373
722
  end
374
723
 
375
724
  it 'sets the default SMB1 Dialect' do
376
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
725
+ expect(client.smb1_negotiate_request.dialects).to include(
726
+ buffer_format: 2,
727
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
728
+ )
377
729
  end
378
730
 
379
731
  it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
380
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
732
+ expect(client.smb1_negotiate_request.dialects).to include(
733
+ buffer_format: 2,
734
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
735
+ )
381
736
  end
382
737
 
383
738
  it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
384
- expect(smb1_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
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
+ )
385
743
  end
386
744
 
387
745
  it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
388
- expect(smb2_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
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
+ )
389
771
  end
390
772
  end
391
773
 
392
- describe '#smb2_negotiate_request' do
774
+ describe '#smb2_3_negotiate_request' do
393
775
  it 'return an SMB2 Negotiate Request packet' do
394
- 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)
395
777
  end
396
778
 
397
- it 'sets the default SMB2 Dialect' do
398
- 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
+ )
399
789
  end
400
790
 
401
791
  it 'sets the Message ID to 0' do
402
- 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
403
877
  end
404
878
  end
405
879
 
@@ -414,10 +888,15 @@ RSpec.describe RubySMB::Client do
414
888
  client.negotiate_request
415
889
  end
416
890
 
417
- it 'calls #smb2_negotiate_request if SMB2 is enabled' do
418
- 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)
419
893
  smb2_client.negotiate_request
420
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
421
900
  end
422
901
 
423
902
  describe '#negotiate_response' do
@@ -464,12 +943,28 @@ RSpec.describe RubySMB::Client do
464
943
  end
465
944
  end
466
945
 
467
- 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
468
963
  it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
469
964
  expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
470
965
  end
471
966
 
472
- 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
473
968
  expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
474
969
  end
475
970
  end
@@ -477,9 +972,10 @@ RSpec.describe RubySMB::Client do
477
972
 
478
973
  describe '#parse_negotiate_response' do
479
974
  context 'when SMB1 was Negotiated' do
480
- it 'turns off SMB2 support' do
975
+ it 'turns off SMB2 and SMB3 support' do
481
976
  client.parse_negotiate_response(smb1_extended_response)
482
977
  expect(client.smb2).to be false
978
+ expect(client.smb3).to be false
483
979
  end
484
980
 
485
981
  it 'sets whether or not signing is required' do
@@ -502,12 +998,18 @@ RSpec.describe RubySMB::Client do
502
998
  it 'returns the string \'SMB1\'' do
503
999
  expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
504
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
505
1006
  end
506
1007
 
507
1008
  context 'when SMB2 was negotiated' do
508
- it 'turns off SMB1 support' do
1009
+ it 'turns off SMB1 and SMB3 support' do
509
1010
  client.parse_negotiate_response(smb2_response)
510
1011
  expect(client.smb1).to be false
1012
+ expect(client.smb3).to be false
511
1013
  end
512
1014
 
513
1015
  it 'sets whether or not signing is required' do
@@ -526,9 +1028,81 @@ RSpec.describe RubySMB::Client do
526
1028
  expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
527
1029
  end
528
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
529
1095
  end
530
1096
 
531
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
+
532
1106
  it 'calls the backing methods' do
533
1107
  expect(client).to receive(:negotiate_request)
534
1108
  expect(client).to receive(:send_recv)
@@ -537,18 +1111,264 @@ RSpec.describe RubySMB::Client do
537
1111
  client.negotiate
538
1112
  end
539
1113
 
540
- it 'sets the response-packet #dialects array with the dialects sent in the request' do
541
- request_packet = client.smb1_negotiate_request
542
- allow(client).to receive(:negotiate_request).and_return(request_packet)
543
- allow(client).to receive(:send_recv)
544
- allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
545
- expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
546
- client.negotiate
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
547
1146
  end
548
1147
 
549
- it 'raise the expected exception if an error occurs' do
550
- allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
551
- expect { client.negotiate }.to raise_error(RubySMB::Error::NegotiationFailure)
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 }
1205
+
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
1213
+ end
1214
+ end
1215
+
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
1225
+ end
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
1232
+ end
1233
+
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)
1238
+ end
1239
+
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
1285
+ end
1286
+
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
1294
+
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
1366
+
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
552
1372
  end
553
1373
  end
554
1374
  end
@@ -875,6 +1695,39 @@ RSpec.describe RubySMB::Client do
875
1695
  smb2_client.smb2_authenticate
876
1696
  expect(smb2_client.os_version).to eq '6.1.7601'
877
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
878
1731
  end
879
1732
 
880
1733
  describe '#smb2_ntlmssp_negotiate_packet' do
@@ -890,20 +1743,34 @@ RSpec.describe RubySMB::Client do
890
1743
  smb2_client.smb2_ntlmssp_negotiate_packet
891
1744
  end
892
1745
 
893
- it 'sets the message ID in the packet header to 1' do
894
- expect(smb2_client.smb2_ntlmssp_negotiate_packet.smb2_header.message_id).to eq 1
895
- end
896
-
897
- it 'increments client#smb2_message_id' do
898
- expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
1746
+ it 'enables signing' do
1747
+ expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
899
1748
  end
900
1749
  end
901
1750
 
902
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
+
903
1757
  it 'sends the request packet and receives a response' do
904
- expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
905
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
906
- expect(dispatcher).to receive(:recv_packet)
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)
907
1774
  smb2_client.smb2_ntlmssp_negotiate
908
1775
  end
909
1776
  end
@@ -961,13 +1828,35 @@ RSpec.describe RubySMB::Client do
961
1828
  it 'sets the session ID on the request packet' do
962
1829
  expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
963
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
964
1835
  end
965
1836
 
966
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
+
967
1843
  it 'sends the request packet and receives a response' do
968
- expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
969
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
970
- expect(dispatcher).to receive(:recv_packet)
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)
971
1860
  smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
972
1861
  end
973
1862
  end
@@ -1103,6 +1992,108 @@ RSpec.describe RubySMB::Client do
1103
1992
  end
1104
1993
  end
1105
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
1106
2097
  end
1107
2098
 
1108
2099
  context '#increment_smb_message_id' do
@@ -1156,7 +2147,10 @@ RSpec.describe RubySMB::Client do
1156
2147
 
1157
2148
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1158
2149
  response.smb_header.nt_status = 0xc0000015
1159
- expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
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
+ )
1160
2154
  end
1161
2155
 
1162
2156
  it 'creates a new Tree from itself, the share path, and the response packet' do
@@ -1177,11 +2171,14 @@ RSpec.describe RubySMB::Client do
1177
2171
  }
1178
2172
 
1179
2173
  describe '#smb2_tree_connect' do
1180
- it 'builds and sends a TreeconnectRequest for the supplied share' do
2174
+ it 'builds and sends the expected TreeconnectRequest for the supplied share' do
1181
2175
  allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
1182
- modified_request = request
1183
- modified_request.encode_path(path)
1184
- expect(smb2_client).to receive(:send_recv).with(modified_request).and_return(response.to_binary_s)
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
1185
2182
  smb2_client.smb2_tree_connect(path)
1186
2183
  end
1187
2184
 
@@ -1200,11 +2197,20 @@ RSpec.describe RubySMB::Client do
1200
2197
 
1201
2198
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1202
2199
  response.smb2_header.nt_status = 0xc0000015
1203
- expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
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
+ )
1204
2204
  end
1205
2205
 
1206
2206
  it 'creates a new Tree from itself, the share path, and the response packet' do
1207
- expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
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)
1208
2214
  smb2_client.smb2_tree_from_response(path, response)
1209
2215
  end
1210
2216
  end
@@ -1301,7 +2307,7 @@ RSpec.describe RubySMB::Client do
1301
2307
  end
1302
2308
 
1303
2309
  it 'raise an InvalidPacket exception when the response is not valid' do
1304
- echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
2310
+ echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
1305
2311
  allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
1306
2312
  expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
1307
2313
  end
@@ -1473,5 +2479,198 @@ RSpec.describe RubySMB::Client do
1473
2479
  end
1474
2480
  end
1475
2481
  end
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
1476
2675
  end
1477
2676