winrm 2.2.3 → 2.3.6

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 (127) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/bin/rwinrm +90 -97
  4. data/lib/winrm/connection.rb +84 -86
  5. data/lib/winrm/connection_opts.rb +90 -91
  6. data/lib/winrm/exceptions.rb +14 -2
  7. data/lib/winrm/http/response_handler.rb +127 -96
  8. data/lib/winrm/http/transport.rb +462 -427
  9. data/lib/winrm/http/transport_factory.rb +1 -5
  10. data/lib/winrm/output.rb +1 -2
  11. data/lib/winrm/psrp/fragment.rb +0 -2
  12. data/lib/winrm/psrp/message.rb +128 -130
  13. data/lib/winrm/psrp/message_data/base.rb +0 -2
  14. data/lib/winrm/psrp/message_data/error_record.rb +2 -2
  15. data/lib/winrm/psrp/message_data/pipeline_host_call.rb +0 -2
  16. data/lib/winrm/psrp/message_data/pipeline_output.rb +48 -54
  17. data/lib/winrm/psrp/message_data/pipeline_state.rb +0 -2
  18. data/lib/winrm/psrp/message_data/runspacepool_host_call.rb +0 -2
  19. data/lib/winrm/psrp/message_data/runspacepool_state.rb +0 -2
  20. data/lib/winrm/psrp/message_data/session_capability.rb +0 -2
  21. data/lib/winrm/psrp/message_data.rb +0 -2
  22. data/lib/winrm/psrp/message_defragmenter.rb +2 -2
  23. data/lib/winrm/psrp/message_factory.rb +16 -5
  24. data/lib/winrm/psrp/message_fragmenter.rb +1 -3
  25. data/lib/winrm/psrp/powershell_output_decoder.rb +142 -144
  26. data/lib/winrm/psrp/receive_response_reader.rb +3 -5
  27. data/lib/winrm/psrp/uuid.rb +1 -2
  28. data/lib/winrm/shells/base.rb +7 -4
  29. data/lib/winrm/shells/cmd.rb +63 -65
  30. data/lib/winrm/shells/power_shell.rb +207 -202
  31. data/lib/winrm/shells/retryable.rb +44 -45
  32. data/lib/winrm/shells/shell_factory.rb +0 -2
  33. data/lib/winrm/version.rb +1 -3
  34. data/lib/winrm/wsmv/base.rb +0 -2
  35. data/lib/winrm/wsmv/cleanup_command.rb +1 -2
  36. data/lib/winrm/wsmv/close_shell.rb +1 -2
  37. data/lib/winrm/wsmv/command.rb +2 -3
  38. data/lib/winrm/wsmv/command_output.rb +2 -3
  39. data/lib/winrm/wsmv/command_output_decoder.rb +1 -2
  40. data/lib/winrm/wsmv/configuration.rb +0 -2
  41. data/lib/winrm/wsmv/create_pipeline.rb +0 -2
  42. data/lib/winrm/wsmv/create_shell.rb +2 -6
  43. data/lib/winrm/wsmv/header.rb +213 -215
  44. data/lib/winrm/wsmv/init_runspace_pool.rb +96 -95
  45. data/lib/winrm/wsmv/iso8601_duration.rb +0 -2
  46. data/lib/winrm/wsmv/keep_alive.rb +0 -2
  47. data/lib/winrm/wsmv/receive_response_reader.rb +128 -126
  48. data/lib/winrm/wsmv/send_data.rb +0 -2
  49. data/lib/winrm/wsmv/soap.rb +0 -2
  50. data/lib/winrm/wsmv/wql_pull.rb +54 -56
  51. data/lib/winrm/wsmv/wql_query.rb +98 -99
  52. data/lib/winrm/wsmv/write_stdin.rb +0 -2
  53. data/lib/winrm.rb +3 -5
  54. metadata +81 -135
  55. data/.gitignore +0 -10
  56. data/.rubocop.yml +0 -26
  57. data/.travis.yml +0 -11
  58. data/Gemfile +0 -3
  59. data/Rakefile +0 -34
  60. data/Vagrantfile +0 -6
  61. data/WinrmAppveyor.psm1 +0 -32
  62. data/appveyor.yml +0 -51
  63. data/changelog.md +0 -128
  64. data/preamble +0 -17
  65. data/tests/integration/auth_timeout_spec.rb +0 -18
  66. data/tests/integration/cmd_spec.rb +0 -131
  67. data/tests/integration/config-example.yml +0 -16
  68. data/tests/integration/issue_59_spec.rb +0 -26
  69. data/tests/integration/powershell_spec.rb +0 -165
  70. data/tests/integration/spec_helper.rb +0 -65
  71. data/tests/integration/transport_spec.rb +0 -99
  72. data/tests/integration/wql_spec.rb +0 -34
  73. data/tests/matchers.rb +0 -60
  74. data/tests/spec/configuration_spec.rb +0 -184
  75. data/tests/spec/connection_spec.rb +0 -39
  76. data/tests/spec/exception_spec.rb +0 -50
  77. data/tests/spec/http/transport_factory_spec.rb +0 -68
  78. data/tests/spec/http/transport_spec.rb +0 -44
  79. data/tests/spec/output_spec.rb +0 -127
  80. data/tests/spec/psrp/fragment_spec.rb +0 -62
  81. data/tests/spec/psrp/message_data/base_spec.rb +0 -13
  82. data/tests/spec/psrp/message_data/error_record_spec.rb +0 -41
  83. data/tests/spec/psrp/message_data/pipeline_host_call_spec.rb +0 -25
  84. data/tests/spec/psrp/message_data/pipeline_output_spec.rb +0 -32
  85. data/tests/spec/psrp/message_data/pipeline_state_spec.rb +0 -40
  86. data/tests/spec/psrp/message_data/runspace_pool_host_call_spec.rb +0 -25
  87. data/tests/spec/psrp/message_data/runspacepool_state_spec.rb +0 -16
  88. data/tests/spec/psrp/message_data/session_capability_spec.rb +0 -30
  89. data/tests/spec/psrp/message_data_spec.rb +0 -35
  90. data/tests/spec/psrp/message_defragmenter_spec.rb +0 -47
  91. data/tests/spec/psrp/message_fragmenter_spec.rb +0 -105
  92. data/tests/spec/psrp/powershell_output_decoder_spec.rb +0 -100
  93. data/tests/spec/psrp/psrp_message_spec.rb +0 -70
  94. data/tests/spec/psrp/recieve_response_reader_spec.rb +0 -172
  95. data/tests/spec/psrp/uuid_spec.rb +0 -28
  96. data/tests/spec/response_handler_spec.rb +0 -61
  97. data/tests/spec/shells/base_spec.rb +0 -225
  98. data/tests/spec/shells/cmd_spec.rb +0 -75
  99. data/tests/spec/shells/powershell_spec.rb +0 -175
  100. data/tests/spec/spec_helper.rb +0 -47
  101. data/tests/spec/stubs/clixml/error_record.xml.erb +0 -84
  102. data/tests/spec/stubs/clixml/pipeline_state.xml.erb +0 -88
  103. data/tests/spec/stubs/responses/get_command_output_response.xml.erb +0 -13
  104. data/tests/spec/stubs/responses/get_command_output_response_not_done.xml.erb +0 -10
  105. data/tests/spec/stubs/responses/get_powershell_keepalive_response.xml.erb +0 -10
  106. data/tests/spec/stubs/responses/get_powershell_output_response.xml.erb +0 -12
  107. data/tests/spec/stubs/responses/get_powershell_output_response_not_done.xml.erb +0 -9
  108. data/tests/spec/stubs/responses/open_shell_v1.xml +0 -19
  109. data/tests/spec/stubs/responses/open_shell_v2.xml +0 -20
  110. data/tests/spec/stubs/responses/soap_fault_v1.xml +0 -36
  111. data/tests/spec/stubs/responses/soap_fault_v2.xml +0 -42
  112. data/tests/spec/stubs/responses/wmi_error_v2.xml +0 -41
  113. data/tests/spec/wsmv/cleanup_command_spec.rb +0 -22
  114. data/tests/spec/wsmv/close_shell_spec.rb +0 -17
  115. data/tests/spec/wsmv/command_output_decoder_spec.rb +0 -37
  116. data/tests/spec/wsmv/command_output_spec.rb +0 -45
  117. data/tests/spec/wsmv/command_spec.rb +0 -19
  118. data/tests/spec/wsmv/configuration_spec.rb +0 -17
  119. data/tests/spec/wsmv/create_pipeline_spec.rb +0 -31
  120. data/tests/spec/wsmv/create_shell_spec.rb +0 -38
  121. data/tests/spec/wsmv/init_runspace_pool_spec.rb +0 -36
  122. data/tests/spec/wsmv/keep_alive_spec.rb +0 -21
  123. data/tests/spec/wsmv/receive_response_reader_spec.rb +0 -123
  124. data/tests/spec/wsmv/send_data_spec.rb +0 -30
  125. data/tests/spec/wsmv/wql_query_spec.rb +0 -13
  126. data/tests/spec/wsmv/write_stdin_spec.rb +0 -22
  127. data/winrm.gemspec +0 -47
@@ -1,427 +1,462 @@
1
- # encoding: UTF-8
2
- #
3
- # Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- require 'httpclient'
18
- require_relative 'response_handler'
19
-
20
- module WinRM
21
- module HTTP
22
- # A generic HTTP transport that utilized HTTPClient to send messages back and forth.
23
- # This backend will maintain state for every WinRMWebService instance that is instantiated so it
24
- # is possible to use GSSAPI with Keep-Alive.
25
- class HttpTransport
26
- attr_reader :endpoint
27
-
28
- def initialize(endpoint, options)
29
- @endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
30
- @httpcli = HTTPClient.new(agent_name: 'Ruby WinRM Client')
31
- @logger = Logging.logger[self]
32
- @httpcli.receive_timeout = options[:receive_timeout]
33
- end
34
-
35
- # Sends the SOAP payload to the WinRM service and returns the service's
36
- # SOAP response. If an error occurrs an appropriate error is raised.
37
- #
38
- # @param [String] The XML SOAP message
39
- # @returns [REXML::Document] The parsed response body
40
- def send_request(message)
41
- ssl_peer_fingerprint_verification!
42
- log_soap_message(message)
43
- hdr = { 'Content-Type' => 'application/soap+xml;charset=UTF-8',
44
- 'Content-Length' => message.bytesize }
45
- # We need to add this header if using Client Certificate authentication
46
- unless @httpcli.ssl_config.client_cert.nil?
47
- hdr['Authorization'] = 'http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual'
48
- end
49
-
50
- resp = @httpcli.post(@endpoint, message, hdr)
51
- log_soap_message(resp.http_body.content)
52
- verify_ssl_fingerprint(resp.peer_cert)
53
- handler = WinRM::ResponseHandler.new(resp.http_body.content, resp.status)
54
- handler.parse_to_xml
55
- end
56
-
57
- # We'll need this to force basic authentication if desired
58
- def basic_auth_only!
59
- auths = @httpcli.www_auth.instance_variable_get('@authenticator')
60
- auths.delete_if { |i| i.scheme !~ /basic/i }
61
- end
62
-
63
- # Disable SSPI Auth
64
- def no_sspi_auth!
65
- auths = @httpcli.www_auth.instance_variable_get('@authenticator')
66
- auths.delete_if { |i| i.is_a? HTTPClient::SSPINegotiateAuth }
67
- end
68
-
69
- # Disable SSL Peer Verification
70
- def no_ssl_peer_verification!
71
- @httpcli.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
72
- end
73
-
74
- # SSL Peer Fingerprint Verification prior to connecting
75
- def ssl_peer_fingerprint_verification!
76
- return unless @ssl_peer_fingerprint && ! @ssl_peer_fingerprint_verified
77
-
78
- with_untrusted_ssl_connection do |connection|
79
- connection_cert = connection.peer_cert_chain.last
80
- verify_ssl_fingerprint(connection_cert)
81
- end
82
- @logger.info("initial ssl fingerprint #{@ssl_peer_fingerprint} verified\n")
83
- @ssl_peer_fingerprint_verified = true
84
- no_ssl_peer_verification!
85
- end
86
-
87
- # Connect without verification to retrieve untrusted ssl context
88
- def with_untrusted_ssl_connection
89
- noverify_peer_context = OpenSSL::SSL::SSLContext.new
90
- noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
91
- tcp_connection = TCPSocket.new(@endpoint.host, @endpoint.port)
92
- begin
93
- ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_context)
94
- ssl_connection.connect
95
- yield ssl_connection
96
- ensure
97
- tcp_connection.close
98
- end
99
- end
100
-
101
- # compare @ssl_peer_fingerprint to current ssl context
102
- def verify_ssl_fingerprint(cert)
103
- return unless @ssl_peer_fingerprint
104
- conn_fingerprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
105
- return unless @ssl_peer_fingerprint.casecmp(conn_fingerprint) != 0
106
- raise "ssl fingerprint mismatch!!!!\n"
107
- end
108
-
109
- protected
110
-
111
- def body(message, length, type = 'application/HTTP-SPNEGO-session-encrypted')
112
- [
113
- '--Encrypted Boundary',
114
- "Content-Type: #{type}",
115
- "OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{length}",
116
- '--Encrypted Boundary',
117
- 'Content-Type: application/octet-stream',
118
- "#{message}--Encrypted Boundary--"
119
- ].join("\r\n").concat("\r\n")
120
- end
121
-
122
- def log_soap_message(message)
123
- return unless @logger.debug?
124
-
125
- xml_msg = REXML::Document.new(message)
126
- formatter = REXML::Formatters::Pretty.new(2)
127
- formatter.compact = true
128
- formatter.write(xml_msg, @logger)
129
- @logger.debug("\n")
130
- rescue StandardError => e
131
- @logger.debug("Couldn't log SOAP request/response: #{e.message} - #{message}")
132
- end
133
- end
134
-
135
- # Plain text, insecure, HTTP transport
136
- class HttpPlaintext < HttpTransport
137
- def initialize(endpoint, user, pass, opts)
138
- super(endpoint, opts)
139
- @httpcli.set_auth(nil, user, pass)
140
- no_sspi_auth! if opts[:disable_sspi]
141
- basic_auth_only! if opts[:basic_auth_only]
142
- no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
143
- end
144
- end
145
-
146
- # NTLM/Negotiate, secure, HTTP transport
147
- class HttpNegotiate < HttpTransport
148
- def initialize(endpoint, user, pass, opts)
149
- super(endpoint, opts)
150
- require 'rubyntlm'
151
- no_sspi_auth!
152
-
153
- user_parts = user.split('\\')
154
- if user_parts.length > 1
155
- opts[:domain] = user_parts[0]
156
- user = user_parts[1]
157
- end
158
-
159
- @ntlmcli = Net::NTLM::Client.new(user, pass, opts)
160
- @retryable = true
161
- no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
162
- @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
163
- @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
164
- end
165
-
166
- def send_request(message, auth_header = nil)
167
- ssl_peer_fingerprint_verification!
168
- auth_header = init_auth if @ntlmcli.session.nil?
169
- log_soap_message(message)
170
-
171
- hdr = {
172
- 'Content-Type' => 'multipart/encrypted;'\
173
- 'protocol="application/HTTP-SPNEGO-session-encrypted";boundary="Encrypted Boundary"'
174
- }
175
- hdr.merge!(auth_header) if auth_header
176
-
177
- resp = @httpcli.post(@endpoint, body(seal(message), message.bytesize), hdr)
178
- verify_ssl_fingerprint(resp.peer_cert)
179
- if resp.status == 401 && @retryable
180
- @retryable = false
181
- send_request(message, init_auth)
182
- else
183
- @retryable = true
184
- decrypted_body = winrm_decrypt(resp.body)
185
- log_soap_message(decrypted_body)
186
- WinRM::ResponseHandler.new(decrypted_body, resp.status).parse_to_xml
187
- end
188
- end
189
-
190
- private
191
-
192
- def seal(message)
193
- emessage = @ntlmcli.session.seal_message message
194
- signature = @ntlmcli.session.sign_message message
195
- "\x10\x00\x00\x00#{signature}#{emessage}"
196
- end
197
-
198
- def winrm_decrypt(str)
199
- return '' if str.empty?
200
- str.force_encoding('BINARY')
201
- str.sub!(%r{^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$}m, '\1')
202
-
203
- signature = str[4..19]
204
- message = @ntlmcli.session.unseal_message str[20..-1]
205
- if @ntlmcli.session.verify_signature(signature, message)
206
- message
207
- else
208
- raise WinRMHTTPTransportError, 'Could not decrypt NTLM message.'
209
- end
210
- end
211
-
212
- def init_auth
213
- @logger.debug "Initializing Negotiate for #{@endpoint}"
214
- auth1 = @ntlmcli.init_context
215
- hdr = {
216
- 'Authorization' => "Negotiate #{auth1.encode64}",
217
- 'Content-Type' => 'application/soap+xml;charset=UTF-8'
218
- }
219
- @logger.debug 'Sending HTTP POST for Negotiate Authentication'
220
- r = @httpcli.post(@endpoint, '', hdr)
221
- verify_ssl_fingerprint(r.peer_cert)
222
- auth_header = r.header['WWW-Authenticate'].pop
223
- unless auth_header
224
- msg = "Unable to parse authorization header. Headers: #{r.headers}\r\nBody: #{r.body}"
225
- raise WinRMHTTPTransportError.new(msg, r.status_code)
226
- end
227
- itok = auth_header.split.last
228
- binding = r.peer_cert.nil? ? nil : Net::NTLM::ChannelBinding.create(r.peer_cert)
229
- auth3 = @ntlmcli.init_context(itok, binding)
230
- { 'Authorization' => "Negotiate #{auth3.encode64}" }
231
- end
232
- end
233
-
234
- # Uses SSL to secure the transport
235
- class BasicAuthSSL < HttpTransport
236
- def initialize(endpoint, user, pass, opts)
237
- super(endpoint, opts)
238
- @httpcli.set_auth(endpoint, user, pass)
239
- basic_auth_only!
240
- no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
241
- @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
242
- @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
243
- end
244
- end
245
-
246
- # Uses Client Certificate to authenticate and SSL to secure the transport
247
- class ClientCertAuthSSL < HttpTransport
248
- def initialize(endpoint, client_cert, client_key, key_pass, opts)
249
- super(endpoint, opts)
250
- @httpcli.ssl_config.set_client_cert_file(client_cert, client_key, key_pass)
251
- @httpcli.www_auth.instance_variable_set('@authenticator', [])
252
- no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
253
- @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
254
- @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
255
- end
256
- end
257
-
258
- # Uses Kerberos/GSSAPI to authenticate and encrypt messages
259
- class HttpGSSAPI < HttpTransport
260
- # @param [String,URI] endpoint the WinRM webservice endpoint
261
- # @param [String] realm the Kerberos realm we are authenticating to
262
- # @param [String<optional>] service the service name, default is HTTP
263
- def initialize(endpoint, realm, opts, service = nil)
264
- require 'gssapi'
265
- require 'gssapi/extensions'
266
-
267
- super(endpoint, opts)
268
- # Remove the GSSAPI auth from HTTPClient because we are doing our own thing
269
- no_sspi_auth!
270
- service ||= 'HTTP'
271
- @service = "#{service}/#{@endpoint.host}@#{realm}"
272
- no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
273
- init_krb
274
- end
275
-
276
- # Sends the SOAP payload to the WinRM service and returns the service's
277
- # SOAP response. If an error occurrs an appropriate error is raised.
278
- #
279
- # @param [String] The XML SOAP message
280
- # @returns [REXML::Document] The parsed response body
281
- def send_request(message)
282
- resp = send_kerberos_request(message)
283
-
284
- if resp.status == 401
285
- @logger.debug 'Got 401 - reinitializing Kerberos and retrying one more time'
286
- init_krb
287
- resp = send_kerberos_request(message)
288
- end
289
-
290
- handler = WinRM::ResponseHandler.new(winrm_decrypt(resp.http_body.content), resp.status)
291
- handler.parse_to_xml
292
- end
293
-
294
- private
295
-
296
- # Sends the SOAP payload to the WinRM service and returns the service's
297
- # HTTP response.
298
- #
299
- # @param [String] The XML SOAP message
300
- # @returns [Object] The HTTP response object
301
- def send_kerberos_request(message)
302
- log_soap_message(message)
303
- original_length = message.bytesize
304
- pad_len, emsg = winrm_encrypt(message)
305
- req_length = original_length + pad_len
306
- hdr = {
307
- 'Connection' => 'Keep-Alive',
308
- 'Content-Type' => 'multipart/encrypted;' \
309
- 'protocol="application/HTTP-Kerberos-session-encrypted";boundary="Encrypted Boundary"'
310
- }
311
-
312
- resp = @httpcli.post(
313
- @endpoint,
314
- body(emsg, req_length, 'application/HTTP-Kerberos-session-encrypted'),
315
- hdr
316
- )
317
- log_soap_message(resp.http_body.content)
318
- resp
319
- end
320
-
321
- def init_krb
322
- @logger.debug "Initializing Kerberos for #{@service}"
323
- @gsscli = GSSAPI::Simple.new(@endpoint.host, @service)
324
- token = @gsscli.init_context
325
- auth = Base64.strict_encode64 token
326
-
327
- hdr = {
328
- 'Authorization' => "Kerberos #{auth}",
329
- 'Connection' => 'Keep-Alive',
330
- 'Content-Type' => 'application/soap+xml;charset=UTF-8'
331
- }
332
- @logger.debug 'Sending HTTP POST for Kerberos Authentication'
333
- r = @httpcli.post(@endpoint, '', hdr)
334
- itok = r.header['WWW-Authenticate'].pop
335
- itok = itok.split.last
336
- itok = Base64.strict_decode64(itok)
337
- @gsscli.init_context(itok)
338
- end
339
-
340
- # rubocop:disable Metrics/MethodLength
341
- # rubocop:disable Metrics/AbcSize
342
-
343
- # @return [String] the encrypted request string
344
- def winrm_encrypt(str)
345
- @logger.debug "Encrypting SOAP message:\n#{str}"
346
- iov_cnt = 3
347
- iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
348
-
349
- iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
350
- iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
351
- GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
352
-
353
- iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
354
- FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1)))
355
- iov1[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
356
- iov1[:buffer].value = str
357
-
358
- iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
359
- FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2)))
360
- iov2[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_PADDING | \
361
- GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
362
-
363
- conf_state = FFI::MemoryPointer.new :uint32
364
- min_stat = FFI::MemoryPointer.new :uint32
365
-
366
- GSSAPI::LibGSSAPI.gss_wrap_iov(
367
- min_stat,
368
- @gsscli.context,
369
- 1,
370
- GSSAPI::LibGSSAPI::GSS_C_QOP_DEFAULT,
371
- conf_state,
372
- iov,
373
- iov_cnt)
374
-
375
- token = [iov0[:buffer].length].pack('L')
376
- token += iov0[:buffer].value
377
- token += iov1[:buffer].value
378
- pad_len = iov2[:buffer].length
379
- token += iov2[:buffer].value if pad_len > 0
380
- [pad_len, token]
381
- end
382
-
383
- # @return [String] the unencrypted response string
384
- def winrm_decrypt(str)
385
- @logger.debug "Decrypting SOAP message:\n#{str}"
386
- iov_cnt = 3
387
- iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
388
-
389
- iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
390
- iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
391
- GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
392
-
393
- iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
394
- FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1)))
395
- iov1[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
396
-
397
- iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
398
- FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2)))
399
- iov2[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
400
-
401
- str.force_encoding('BINARY')
402
- str.sub!(%r{^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$}m, '\1')
403
-
404
- len = str.unpack('L').first
405
- iov_data = str.unpack("LA#{len}A*")
406
- iov0[:buffer].value = iov_data[1]
407
- iov1[:buffer].value = iov_data[2]
408
-
409
- min_stat = FFI::MemoryPointer.new :uint32
410
- conf_state = FFI::MemoryPointer.new :uint32
411
- conf_state.write_int(1)
412
- qop_state = FFI::MemoryPointer.new :uint32
413
- qop_state.write_int(0)
414
-
415
- maj_stat = GSSAPI::LibGSSAPI.gss_unwrap_iov(
416
- min_stat, @gsscli.context, conf_state, qop_state, iov, iov_cnt)
417
-
418
- @logger.debug "SOAP message decrypted (MAJ: #{maj_stat}, " \
419
- "MIN: #{min_stat.read_int}):\n#{iov1[:buffer].value}"
420
-
421
- iov1[:buffer].value
422
- end
423
- # rubocop:enable Metrics/MethodLength
424
- # rubocop:enable Metrics/AbcSize
425
- end
426
- end
427
- end # WinRM
1
+ # Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'httpclient'
16
+ require_relative 'response_handler'
17
+
18
+ module WinRM
19
+ module HTTP
20
+ # A generic HTTP transport that utilized HTTPClient to send messages back and forth.
21
+ # This backend will maintain state for every WinRMWebService instance that is instantiated so it
22
+ # is possible to use GSSAPI with Keep-Alive.
23
+ class HttpTransport
24
+ attr_reader :endpoint
25
+
26
+ def initialize(endpoint, options)
27
+ @endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
28
+ @httpcli = HTTPClient.new(agent_name: 'Ruby WinRM Client')
29
+ @logger = Logging.logger[self]
30
+ @httpcli.receive_timeout = options[:receive_timeout]
31
+ end
32
+
33
+ # Sends the SOAP payload to the WinRM service and returns the service's
34
+ # SOAP response. If an error occurrs an appropriate error is raised.
35
+ #
36
+ # @param [String] The XML SOAP message
37
+ # @returns [REXML::Document] The parsed response body
38
+ def send_request(message)
39
+ ssl_peer_fingerprint_verification!
40
+ log_soap_message(message)
41
+ hdr = { 'Content-Type' => 'application/soap+xml;charset=UTF-8',
42
+ 'Content-Length' => message.bytesize }
43
+ # We need to add this header if using Client Certificate authentication
44
+ unless @httpcli.ssl_config.client_cert.nil?
45
+ hdr['Authorization'] = 'http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual'
46
+ end
47
+
48
+ resp = @httpcli.post(@endpoint, message, hdr)
49
+ log_soap_message(resp.http_body.content)
50
+ verify_ssl_fingerprint(resp.peer_cert)
51
+ handler = WinRM::ResponseHandler.new(resp.http_body.content, resp.status)
52
+ handler.parse_to_xml
53
+ end
54
+
55
+ # We'll need this to force basic authentication if desired
56
+ def basic_auth_only!
57
+ auths = @httpcli.www_auth.instance_variable_get('@authenticator')
58
+ auths.delete_if { |i| i.scheme !~ /basic/i }
59
+ end
60
+
61
+ # Disable SSPI Auth
62
+ def no_sspi_auth!
63
+ auths = @httpcli.www_auth.instance_variable_get('@authenticator')
64
+ auths.delete_if { |i| i.is_a? HTTPClient::SSPINegotiateAuth }
65
+ end
66
+
67
+ # Disable SSL Peer Verification
68
+ def no_ssl_peer_verification!
69
+ @httpcli.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
70
+ end
71
+
72
+ # SSL Peer Fingerprint Verification prior to connecting
73
+ def ssl_peer_fingerprint_verification!
74
+ return unless @ssl_peer_fingerprint && !@ssl_peer_fingerprint_verified
75
+
76
+ with_untrusted_ssl_connection do |connection|
77
+ connection_cert = connection.peer_cert
78
+ verify_ssl_fingerprint(connection_cert)
79
+ end
80
+ @logger.info("initial ssl fingerprint #{@ssl_peer_fingerprint} verified\n")
81
+ @ssl_peer_fingerprint_verified = true
82
+ no_ssl_peer_verification!
83
+ end
84
+
85
+ # Connect without verification to retrieve untrusted ssl context
86
+ def with_untrusted_ssl_connection
87
+ noverify_peer_context = OpenSSL::SSL::SSLContext.new
88
+ noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
89
+ tcp_connection = TCPSocket.new(@endpoint.host, @endpoint.port)
90
+ begin
91
+ ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_context)
92
+ ssl_connection.connect
93
+ yield ssl_connection
94
+ ensure
95
+ tcp_connection.close
96
+ end
97
+ end
98
+
99
+ # compare @ssl_peer_fingerprint to current ssl context
100
+ def verify_ssl_fingerprint(cert)
101
+ return unless @ssl_peer_fingerprint
102
+
103
+ conn_fingerprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
104
+ return unless @ssl_peer_fingerprint.casecmp(conn_fingerprint) != 0
105
+
106
+ raise "ssl fingerprint mismatch!!!!\n"
107
+ end
108
+
109
+ protected
110
+
111
+ def body(message, length, type = 'application/HTTP-SPNEGO-session-encrypted')
112
+ [
113
+ '--Encrypted Boundary',
114
+ "Content-Type: #{type}",
115
+ "OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{length}",
116
+ '--Encrypted Boundary',
117
+ 'Content-Type: application/octet-stream',
118
+ "#{message}--Encrypted Boundary--"
119
+ ].join("\r\n").concat("\r\n")
120
+ end
121
+
122
+ def log_soap_message(message)
123
+ return unless @logger.debug?
124
+
125
+ xml_msg = REXML::Document.new(message)
126
+ formatter = REXML::Formatters::Pretty.new(2)
127
+ formatter.compact = true
128
+ formatter.write(xml_msg, @logger)
129
+ @logger.debug("\n")
130
+ rescue StandardError => e
131
+ @logger.debug("Couldn't log SOAP request/response: #{e.message} - #{message}")
132
+ end
133
+ end
134
+
135
+ # Plain text, insecure, HTTP transport
136
+ class HttpPlaintext < HttpTransport
137
+ def initialize(endpoint, user, pass, opts)
138
+ super(endpoint, opts)
139
+ @httpcli.set_auth(nil, user, pass)
140
+ no_sspi_auth! if opts[:disable_sspi]
141
+ basic_auth_only! if opts[:basic_auth_only]
142
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
143
+ end
144
+ end
145
+
146
+ # NTLM/Negotiate, secure, HTTP transport
147
+ class HttpNegotiate < HttpTransport
148
+ def initialize(endpoint, user, pass, opts)
149
+ super(endpoint, opts)
150
+ require 'rubyntlm'
151
+ no_sspi_auth!
152
+
153
+ user_parts = user.split('\\')
154
+ if user_parts.length > 1
155
+ opts[:domain] = user_parts[0]
156
+ user = user_parts[1]
157
+ end
158
+
159
+ @ntlmcli = Net::NTLM::Client.new(user, pass, opts)
160
+ @retryable = true
161
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
162
+ @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
163
+ @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
164
+ end
165
+
166
+ def send_request(message)
167
+ ssl_peer_fingerprint_verification!
168
+ init_auth if @ntlmcli.session.nil?
169
+ log_soap_message(message)
170
+
171
+ hdr = {
172
+ 'Content-Type' => 'multipart/encrypted;'\
173
+ 'protocol="application/HTTP-SPNEGO-session-encrypted";boundary="Encrypted Boundary"'
174
+ }
175
+
176
+ resp = @httpcli.post(@endpoint, body(seal(message), message.bytesize), hdr)
177
+ verify_ssl_fingerprint(resp.peer_cert)
178
+ if resp.status == 401 && @retryable
179
+ @retryable = false
180
+ init_auth
181
+ send_request(message)
182
+ else
183
+ @retryable = true
184
+ decrypted_body = winrm_decrypt(resp)
185
+ log_soap_message(decrypted_body)
186
+ WinRM::ResponseHandler.new(decrypted_body, resp.status).parse_to_xml
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ def seal(message)
193
+ emessage = @ntlmcli.session.seal_message message
194
+ signature = @ntlmcli.session.sign_message message
195
+ "\x10\x00\x00\x00#{signature}#{emessage}"
196
+ end
197
+
198
+ def winrm_decrypt(resp)
199
+ # OMI server doesn't always respond to encrypted messages with encrypted responses over SSL
200
+ return resp.body if resp.header['Content-Type'].first =~ %r{\Aapplication\/soap\+xml}i
201
+ return '' if resp.body.empty?
202
+
203
+ str = resp.body.force_encoding('BINARY')
204
+ str.sub!(%r{^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$}m, '\1')
205
+
206
+ signature = str[4..19]
207
+ message = @ntlmcli.session.unseal_message str[20..-1]
208
+ return message if @ntlmcli.session.verify_signature(signature, message)
209
+
210
+ raise WinRMHTTPTransportError, 'Could not decrypt NTLM message.'
211
+ end
212
+
213
+ def issue_challenge_response(negotiate)
214
+ auth_header = {
215
+ 'Authorization' => "Negotiate #{negotiate.encode64}",
216
+ 'Content-Type' => 'application/soap+xml;charset=UTF-8'
217
+ }
218
+
219
+ # OMI Server on Linux requires an empty payload with the new auth header to proceed
220
+ # because the config check for max payload size will otherwise break the auth handshake
221
+ # given the OMI server does not support that check
222
+ @httpcli.post(@endpoint, '', auth_header)
223
+
224
+ # return an empty hash of headers for subsequent requests to use
225
+ {}
226
+ end
227
+
228
+ def init_auth
229
+ @logger.debug "Initializing Negotiate for #{@endpoint}"
230
+ auth1 = @ntlmcli.init_context
231
+ hdr = {
232
+ 'Authorization' => "Negotiate #{auth1.encode64}",
233
+ 'Content-Type' => 'application/soap+xml;charset=UTF-8'
234
+ }
235
+ @logger.debug 'Sending HTTP POST for Negotiate Authentication'
236
+ r = @httpcli.post(@endpoint, '', hdr)
237
+ verify_ssl_fingerprint(r.peer_cert)
238
+ auth_header = r.header['WWW-Authenticate'].pop
239
+ unless auth_header
240
+ msg = "Unable to parse authorization header. Headers: #{r.headers}\r\nBody: #{r.body}"
241
+ raise WinRMHTTPTransportError.new(msg, r.status_code)
242
+ end
243
+ itok = auth_header.split.last
244
+ auth3 = @ntlmcli.init_context(itok, channel_binding(r))
245
+ issue_challenge_response(auth3)
246
+ end
247
+
248
+ def channel_binding(response)
249
+ if response.peer_cert.nil?
250
+ nil
251
+ else
252
+ cert = if RUBY_PLATFORM == 'java'
253
+ OpenSSL::X509::Certificate.new(response.peer_cert.cert.getEncoded)
254
+ else
255
+ response.peer_cert
256
+ end
257
+ Net::NTLM::ChannelBinding.create(OpenSSL::X509::Certificate.new(cert))
258
+ end
259
+ end
260
+ end
261
+
262
+ # Uses SSL to secure the transport
263
+ class BasicAuthSSL < HttpTransport
264
+ def initialize(endpoint, user, pass, opts)
265
+ super(endpoint, opts)
266
+ @httpcli.set_auth(endpoint, user, pass)
267
+ basic_auth_only!
268
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
269
+ @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
270
+ @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
271
+ end
272
+ end
273
+
274
+ # Uses Client Certificate to authenticate and SSL to secure the transport
275
+ class ClientCertAuthSSL < HttpTransport
276
+ def initialize(endpoint, client_cert, client_key, key_pass, opts)
277
+ super(endpoint, opts)
278
+ @httpcli.ssl_config.set_client_cert_file(client_cert, client_key, key_pass)
279
+ @httpcli.www_auth.instance_variable_set('@authenticator', [])
280
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
281
+ @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
282
+ @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
283
+ end
284
+ end
285
+
286
+ # Uses Kerberos/GSSAPI to authenticate and encrypt messages
287
+ class HttpGSSAPI < HttpTransport
288
+ # @param [String,URI] endpoint the WinRM webservice endpoint
289
+ # @param [String] realm the Kerberos realm we are authenticating to
290
+ # @param [String<optional>] service the service name, default is HTTP
291
+ def initialize(endpoint, realm, opts, service = nil)
292
+ require 'gssapi'
293
+ require 'gssapi/extensions'
294
+
295
+ super(endpoint, opts)
296
+ # Remove the GSSAPI auth from HTTPClient because we are doing our own thing
297
+ no_sspi_auth!
298
+ service ||= 'HTTP'
299
+ @service = "#{service}/#{@endpoint.host}@#{realm}"
300
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
301
+ init_krb
302
+ end
303
+
304
+ # Sends the SOAP payload to the WinRM service and returns the service's
305
+ # SOAP response. If an error occurrs an appropriate error is raised.
306
+ #
307
+ # @param [String] The XML SOAP message
308
+ # @returns [REXML::Document] The parsed response body
309
+ def send_request(message)
310
+ resp = send_kerberos_request(message)
311
+
312
+ if resp.status == 401
313
+ @logger.debug 'Got 401 - reinitializing Kerberos and retrying one more time'
314
+ init_krb
315
+ resp = send_kerberos_request(message)
316
+ end
317
+
318
+ handler = WinRM::ResponseHandler.new(winrm_decrypt(resp.http_body.content), resp.status)
319
+ handler.parse_to_xml
320
+ end
321
+
322
+ private
323
+
324
+ # Sends the SOAP payload to the WinRM service and returns the service's
325
+ # HTTP response.
326
+ #
327
+ # @param [String] The XML SOAP message
328
+ # @returns [Object] The HTTP response object
329
+ def send_kerberos_request(message)
330
+ log_soap_message(message)
331
+ original_length = message.bytesize
332
+ pad_len, emsg = winrm_encrypt(message)
333
+ req_length = original_length + pad_len
334
+ hdr = {
335
+ 'Connection' => 'Keep-Alive',
336
+ 'Content-Type' => 'multipart/encrypted;' \
337
+ 'protocol="application/HTTP-Kerberos-session-encrypted";boundary="Encrypted Boundary"'
338
+ }
339
+
340
+ resp = @httpcli.post(
341
+ @endpoint,
342
+ body(emsg, req_length, 'application/HTTP-Kerberos-session-encrypted'),
343
+ hdr
344
+ )
345
+ log_soap_message(resp.http_body.content)
346
+ resp
347
+ end
348
+
349
+ def init_krb
350
+ @logger.debug "Initializing Kerberos for #{@service}"
351
+ @gsscli = GSSAPI::Simple.new(@endpoint.host, @service)
352
+ token = @gsscli.init_context
353
+ auth = Base64.strict_encode64 token
354
+
355
+ hdr = {
356
+ 'Authorization' => "Kerberos #{auth}",
357
+ 'Connection' => 'Keep-Alive',
358
+ 'Content-Type' => 'application/soap+xml;charset=UTF-8'
359
+ }
360
+ @logger.debug 'Sending HTTP POST for Kerberos Authentication'
361
+ r = @httpcli.post(@endpoint, '', hdr)
362
+ itok = r.header['WWW-Authenticate'].pop
363
+ itok = itok.split.last
364
+ itok = Base64.strict_decode64(itok)
365
+ @gsscli.init_context(itok)
366
+ end
367
+
368
+ # rubocop:disable Metrics/MethodLength
369
+ # rubocop:disable Metrics/AbcSize
370
+
371
+ # @return [String] the encrypted request string
372
+ def winrm_encrypt(str)
373
+ @logger.debug "Encrypting SOAP message:\n#{str}"
374
+ iov_cnt = 3
375
+ iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
376
+
377
+ iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
378
+ iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
379
+ GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
380
+
381
+ iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
382
+ FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1))
383
+ )
384
+ iov1[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
385
+ iov1[:buffer].value = str
386
+
387
+ iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
388
+ FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2))
389
+ )
390
+ iov2[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_PADDING | \
391
+ GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
392
+
393
+ conf_state = FFI::MemoryPointer.new :uint32
394
+ min_stat = FFI::MemoryPointer.new :uint32
395
+
396
+ GSSAPI::LibGSSAPI.gss_wrap_iov(
397
+ min_stat,
398
+ @gsscli.context,
399
+ 1,
400
+ GSSAPI::LibGSSAPI::GSS_C_QOP_DEFAULT,
401
+ conf_state,
402
+ iov,
403
+ iov_cnt
404
+ )
405
+
406
+ token = [iov0[:buffer].length].pack('L')
407
+ token += iov0[:buffer].value
408
+ token += iov1[:buffer].value
409
+ pad_len = iov2[:buffer].length
410
+ token += iov2[:buffer].value if pad_len > 0
411
+ [pad_len, token]
412
+ end
413
+
414
+ # @return [String] the unencrypted response string
415
+ def winrm_decrypt(str)
416
+ @logger.debug "Decrypting SOAP message:\n#{str}"
417
+ iov_cnt = 3
418
+ iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
419
+
420
+ iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
421
+ iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
422
+ GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
423
+
424
+ iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
425
+ FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1))
426
+ )
427
+ iov1[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
428
+
429
+ iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
430
+ FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2))
431
+ )
432
+ iov2[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
433
+
434
+ str.force_encoding('BINARY')
435
+ str.sub!(%r{^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$}m, '\1')
436
+
437
+ len = str.unpack('L').first
438
+ iov_data = str.unpack("La#{len}a*")
439
+ iov0[:buffer].value = iov_data[1]
440
+ iov1[:buffer].value = iov_data[2]
441
+
442
+ min_stat = FFI::MemoryPointer.new :uint32
443
+ conf_state = FFI::MemoryPointer.new :uint32
444
+ conf_state.write_int(1)
445
+ qop_state = FFI::MemoryPointer.new :uint32
446
+ qop_state.write_int(0)
447
+
448
+ maj_stat = GSSAPI::LibGSSAPI.gss_unwrap_iov(
449
+ min_stat, @gsscli.context, conf_state, qop_state, iov, iov_cnt
450
+ )
451
+
452
+ @logger.debug "SOAP message decrypted (MAJ: #{maj_stat}, " \
453
+ "MIN: #{min_stat.read_int}):\n#{iov1[:buffer].value}"
454
+
455
+ iov1[:buffer].value
456
+ end
457
+ # rubocop:enable Metrics/MethodLength
458
+ # rubocop:enable Metrics/AbcSize
459
+ end
460
+ end
461
+ end
462
+ # WinRM