winrm 1.7.1 → 1.7.2
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.
- checksums.yaml +4 -4
- data/.gitignore +10 -10
- data/.rspec +3 -3
- data/.rubocop.yml +12 -12
- data/.travis.yml +12 -12
- data/Gemfile +9 -9
- data/LICENSE +202 -202
- data/README.md +194 -194
- data/Rakefile +36 -36
- data/Vagrantfile +9 -9
- data/appveyor.yml +42 -42
- data/bin/rwinrm +97 -97
- data/changelog.md +77 -74
- data/lib/winrm.rb +42 -42
- data/lib/winrm/command_executor.rb +224 -224
- data/lib/winrm/command_output_decoder.rb +53 -0
- data/lib/winrm/exceptions/exceptions.rb +57 -57
- data/lib/winrm/helpers/iso8601_duration.rb +58 -58
- data/lib/winrm/helpers/powershell_script.rb +42 -42
- data/lib/winrm/http/response_handler.rb +82 -82
- data/lib/winrm/http/transport.rb +421 -421
- data/lib/winrm/output.rb +43 -43
- data/lib/winrm/soap_provider.rb +39 -39
- data/lib/winrm/version.rb +7 -7
- data/lib/winrm/winrm_service.rb +547 -556
- data/preamble +17 -17
- data/spec/auth_timeout_spec.rb +16 -16
- data/spec/cmd_spec.rb +102 -102
- data/spec/command_executor_spec.rb +429 -429
- data/spec/command_output_decoder_spec.rb +37 -0
- data/spec/config-example.yml +19 -19
- data/spec/exception_spec.rb +50 -50
- data/spec/issue_184_spec.rb +67 -67
- data/spec/issue_59_spec.rb +23 -23
- data/spec/matchers.rb +74 -74
- data/spec/output_spec.rb +110 -110
- data/spec/powershell_spec.rb +97 -97
- data/spec/response_handler_spec.rb +59 -59
- data/spec/spec_helper.rb +73 -73
- data/spec/stubs/responses/get_command_output_response.xml.erb +13 -13
- data/spec/stubs/responses/open_shell_v1.xml +19 -19
- data/spec/stubs/responses/open_shell_v2.xml +20 -20
- data/spec/stubs/responses/soap_fault_v1.xml +36 -36
- data/spec/stubs/responses/soap_fault_v2.xml +42 -42
- data/spec/stubs/responses/wmi_error_v2.xml +41 -41
- data/spec/transport_spec.rb +124 -124
- data/spec/winrm_options_spec.rb +76 -76
- data/spec/winrm_primitives_spec.rb +51 -51
- data/spec/wql_spec.rb +14 -14
- data/winrm.gemspec +40 -40
- metadata +5 -3
data/lib/winrm/http/transport.rb
CHANGED
@@ -1,421 +1,421 @@
|
|
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_relative 'response_handler'
|
18
|
-
|
19
|
-
module WinRM
|
20
|
-
module HTTP
|
21
|
-
# A generic HTTP transport that utilized HTTPClient to send messages back and forth.
|
22
|
-
# This backend will maintain state for every WinRMWebService instance that is instantiated so it
|
23
|
-
# is possible to use GSSAPI with Keep-Alive.
|
24
|
-
class HttpTransport
|
25
|
-
# Set this to an unreasonable amount because WinRM has its own timeouts
|
26
|
-
DEFAULT_RECEIVE_TIMEOUT = 3600
|
27
|
-
|
28
|
-
attr_reader :endpoint
|
29
|
-
|
30
|
-
def initialize(endpoint)
|
31
|
-
@endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
|
32
|
-
@httpcli = HTTPClient.new(agent_name: 'Ruby WinRM Client')
|
33
|
-
@httpcli.receive_timeout = DEFAULT_RECEIVE_TIMEOUT
|
34
|
-
@logger = Logging.logger[self]
|
35
|
-
end
|
36
|
-
|
37
|
-
# Sends the SOAP payload to the WinRM service and returns the service's
|
38
|
-
# SOAP response. If an error occurrs an appropriate error is raised.
|
39
|
-
#
|
40
|
-
# @param [String] The XML SOAP message
|
41
|
-
# @returns [REXML::Document] The parsed response body
|
42
|
-
def send_request(message)
|
43
|
-
ssl_peer_fingerprint_verification!
|
44
|
-
log_soap_message(message)
|
45
|
-
hdr = { 'Content-Type' => 'application/soap+xml;charset=UTF-8',
|
46
|
-
'Content-Length' => message.length }
|
47
|
-
resp = @httpcli.post(@endpoint, message, hdr)
|
48
|
-
log_soap_message(resp.http_body.content)
|
49
|
-
verify_ssl_fingerprint(resp.peer_cert)
|
50
|
-
handler = WinRM::ResponseHandler.new(resp.http_body.content, resp.status)
|
51
|
-
handler.parse_to_xml
|
52
|
-
end
|
53
|
-
|
54
|
-
# We'll need this to force basic authentication if desired
|
55
|
-
def basic_auth_only!
|
56
|
-
auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
57
|
-
auths.delete_if { |i| i.scheme !~ /basic/i }
|
58
|
-
end
|
59
|
-
|
60
|
-
# Disable SSPI Auth
|
61
|
-
def no_sspi_auth!
|
62
|
-
auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
63
|
-
auths.delete_if { |i| i.is_a? HTTPClient::SSPINegotiateAuth }
|
64
|
-
end
|
65
|
-
|
66
|
-
# Disable SSL Peer Verification
|
67
|
-
def no_ssl_peer_verification!
|
68
|
-
@httpcli.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
69
|
-
end
|
70
|
-
|
71
|
-
# SSL Peer Fingerprint Verification prior to connecting
|
72
|
-
def ssl_peer_fingerprint_verification!
|
73
|
-
return unless @ssl_peer_fingerprint && ! @ssl_peer_fingerprint_verified
|
74
|
-
|
75
|
-
with_untrusted_ssl_connection do |connection|
|
76
|
-
connection_cert = connection.peer_cert_chain.last
|
77
|
-
verify_ssl_fingerprint(connection_cert)
|
78
|
-
end
|
79
|
-
@logger.info("initial ssl fingerprint #{@ssl_peer_fingerprint} verified\n")
|
80
|
-
@ssl_peer_fingerprint_verified = true
|
81
|
-
no_ssl_peer_verification!
|
82
|
-
end
|
83
|
-
|
84
|
-
# Connect without verification to retrieve untrusted ssl context
|
85
|
-
def with_untrusted_ssl_connection
|
86
|
-
noverify_peer_context = OpenSSL::SSL::SSLContext.new
|
87
|
-
noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
88
|
-
tcp_connection = TCPSocket.new(@endpoint.host, @endpoint.port)
|
89
|
-
begin
|
90
|
-
ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_context)
|
91
|
-
ssl_connection.connect
|
92
|
-
yield ssl_connection
|
93
|
-
ensure
|
94
|
-
tcp_connection.close
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# compare @ssl_peer_fingerprint to current ssl context
|
99
|
-
def verify_ssl_fingerprint(cert)
|
100
|
-
return unless @ssl_peer_fingerprint
|
101
|
-
conn_fingerprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
|
102
|
-
return unless @ssl_peer_fingerprint.casecmp(conn_fingerprint) != 0
|
103
|
-
fail "ssl fingerprint mismatch!!!!\n"
|
104
|
-
end
|
105
|
-
|
106
|
-
# HTTP Client receive timeout. How long should a remote call wait for a
|
107
|
-
# for a response from WinRM?
|
108
|
-
def receive_timeout=(sec)
|
109
|
-
@httpcli.receive_timeout = sec
|
110
|
-
end
|
111
|
-
|
112
|
-
def receive_timeout
|
113
|
-
@httpcli.receive_timeout
|
114
|
-
end
|
115
|
-
|
116
|
-
protected
|
117
|
-
|
118
|
-
def log_soap_message(message)
|
119
|
-
return unless @logger.debug?
|
120
|
-
|
121
|
-
xml_msg = REXML::Document.new(message)
|
122
|
-
formatter = REXML::Formatters::Pretty.new(2)
|
123
|
-
formatter.compact = true
|
124
|
-
formatter.write(xml_msg, @logger)
|
125
|
-
@logger.debug("\n")
|
126
|
-
rescue StandardError => e
|
127
|
-
@logger.debug("Couldn't log SOAP request/response: #{e.message} - #{message}")
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
|
132
|
-
# Plain text, insecure, HTTP transport
|
133
|
-
class HttpPlaintext < HttpTransport
|
134
|
-
def initialize(endpoint, user, pass, opts)
|
135
|
-
super(endpoint)
|
136
|
-
@httpcli.set_auth(nil, user, pass)
|
137
|
-
no_sspi_auth! if opts[:disable_sspi]
|
138
|
-
basic_auth_only! if opts[:basic_auth_only]
|
139
|
-
no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
|
144
|
-
# NTLM/Negotiate, secure, HTTP transport
|
145
|
-
class HttpNegotiate < HttpTransport
|
146
|
-
def initialize(endpoint, user, pass, opts)
|
147
|
-
super(endpoint)
|
148
|
-
require 'rubyntlm'
|
149
|
-
no_sspi_auth!
|
150
|
-
|
151
|
-
user_parts = user.split('\\')
|
152
|
-
if(user_parts.length > 1)
|
153
|
-
opts[:domain] = user_parts[0]
|
154
|
-
user = user_parts[1]
|
155
|
-
end
|
156
|
-
|
157
|
-
@ntlmcli = Net::NTLM::Client.new(user, pass, opts)
|
158
|
-
@retryable = true
|
159
|
-
no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
|
160
|
-
@ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
|
161
|
-
@httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
|
162
|
-
end
|
163
|
-
|
164
|
-
def send_request(message, auth_header = nil)
|
165
|
-
ssl_peer_fingerprint_verification!
|
166
|
-
auth_header = init_auth if @ntlmcli.session.nil?
|
167
|
-
|
168
|
-
original_length = message.length
|
169
|
-
|
170
|
-
emessage = @ntlmcli.session.seal_message message
|
171
|
-
signature = @ntlmcli.session.sign_message message
|
172
|
-
seal = "\x10\x00\x00\x00#{signature}#{emessage}"
|
173
|
-
|
174
|
-
hdr = {
|
175
|
-
"Content-Type" => "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\""
|
176
|
-
}
|
177
|
-
hdr.merge!(auth_header) if auth_header
|
178
|
-
|
179
|
-
body = [
|
180
|
-
"--Encrypted Boundary",
|
181
|
-
"Content-Type: application/HTTP-SPNEGO-session-encrypted",
|
182
|
-
"OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length}",
|
183
|
-
"--Encrypted Boundary",
|
184
|
-
"Content-Type: application/octet-stream",
|
185
|
-
"#{seal}--Encrypted Boundary--",
|
186
|
-
""
|
187
|
-
].join("\r\n")
|
188
|
-
|
189
|
-
resp = @httpcli.post(@endpoint, body, hdr)
|
190
|
-
verify_ssl_fingerprint(resp.peer_cert)
|
191
|
-
if resp.status == 401 && @retryable
|
192
|
-
@retryable = false
|
193
|
-
send_request(message, init_auth)
|
194
|
-
else
|
195
|
-
@retryable = true
|
196
|
-
decrypted_body = resp.body.empty? ? '' : winrm_decrypt(resp.body)
|
197
|
-
handler = WinRM::ResponseHandler.new(decrypted_body, resp.status)
|
198
|
-
handler.parse_to_xml()
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
private
|
203
|
-
|
204
|
-
def winrm_decrypt(str)
|
205
|
-
str.force_encoding('BINARY')
|
206
|
-
str.sub!(/^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$/m, '\1')
|
207
|
-
|
208
|
-
signature = str[4..19]
|
209
|
-
message = @ntlmcli.session.unseal_message str[20..-1]
|
210
|
-
if @ntlmcli.session.verify_signature(signature, message)
|
211
|
-
message
|
212
|
-
else
|
213
|
-
raise WinRMWebServiceError, "Could not verify SOAP message."
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def init_auth
|
218
|
-
@logger.debug "Initializing Negotiate for #{@service}"
|
219
|
-
auth1 = @ntlmcli.init_context
|
220
|
-
hdr = {"Authorization" => "Negotiate #{auth1.encode64}",
|
221
|
-
"Content-Type" => "application/soap+xml;charset=UTF-8"
|
222
|
-
}
|
223
|
-
@logger.debug "Sending HTTP POST for Negotiate Authentication"
|
224
|
-
r = @httpcli.post(@endpoint, "", hdr)
|
225
|
-
verify_ssl_fingerprint(r.peer_cert)
|
226
|
-
itok = r.header["WWW-Authenticate"].pop.split.last
|
227
|
-
binding = r.peer_cert.nil? ? nil : Net::NTLM::ChannelBinding.create(r.peer_cert)
|
228
|
-
auth3 = @ntlmcli.init_context(itok, binding)
|
229
|
-
{ "Authorization" => "Negotiate #{auth3.encode64}" }
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
# Uses SSL to secure the transport
|
234
|
-
class BasicAuthSSL < HttpTransport
|
235
|
-
def initialize(endpoint, user, pass, opts)
|
236
|
-
super(endpoint)
|
237
|
-
@httpcli.set_auth(endpoint, user, pass)
|
238
|
-
basic_auth_only!
|
239
|
-
no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
|
240
|
-
@ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
|
241
|
-
@httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
# Uses Kerberos/GSSAPI to authenticate and encrypt messages
|
246
|
-
# rubocop:disable Metrics/ClassLength
|
247
|
-
class HttpGSSAPI < HttpTransport
|
248
|
-
# @param [String,URI] endpoint the WinRM webservice endpoint
|
249
|
-
# @param [String] realm the Kerberos realm we are authenticating to
|
250
|
-
# @param [String<optional>] service the service name, default is HTTP
|
251
|
-
# @param [String<optional>] keytab the path to a keytab file if you are using one
|
252
|
-
# rubocop:disable Lint/UnusedMethodArgument
|
253
|
-
def initialize(endpoint, realm, service = nil, keytab = nil, opts)
|
254
|
-
# rubocop:enable Lint/UnusedMethodArgument
|
255
|
-
super(endpoint)
|
256
|
-
# Remove the GSSAPI auth from HTTPClient because we are doing our own thing
|
257
|
-
no_sspi_auth!
|
258
|
-
service ||= 'HTTP'
|
259
|
-
@service = "#{service}/#{@endpoint.host}@#{realm}"
|
260
|
-
init_krb
|
261
|
-
end
|
262
|
-
|
263
|
-
# Sends the SOAP payload to the WinRM service and returns the service's
|
264
|
-
# SOAP response. If an error occurrs an appropriate error is raised.
|
265
|
-
#
|
266
|
-
# @param [String] The XML SOAP message
|
267
|
-
# @returns [REXML::Document] The parsed response body
|
268
|
-
def send_request(message)
|
269
|
-
resp = send_kerberos_request(message)
|
270
|
-
|
271
|
-
if resp.status == 401
|
272
|
-
@logger.debug 'Got 401 - reinitializing Kerberos and retrying one more time'
|
273
|
-
init_krb
|
274
|
-
resp = send_kerberos_request(message)
|
275
|
-
end
|
276
|
-
|
277
|
-
handler = WinRM::ResponseHandler.new(winrm_decrypt(resp.http_body.content), resp.status)
|
278
|
-
handler.parse_to_xml
|
279
|
-
end
|
280
|
-
|
281
|
-
private
|
282
|
-
|
283
|
-
# rubocop:disable Metrics/MethodLength
|
284
|
-
# rubocop:disable Metrics/AbcSize
|
285
|
-
|
286
|
-
# Sends the SOAP payload to the WinRM service and returns the service's
|
287
|
-
# HTTP response.
|
288
|
-
#
|
289
|
-
# @param [String] The XML SOAP message
|
290
|
-
# @returns [Object] The HTTP response object
|
291
|
-
def send_kerberos_request(message)
|
292
|
-
log_soap_message(message)
|
293
|
-
original_length = message.length
|
294
|
-
pad_len, emsg = winrm_encrypt(message)
|
295
|
-
hdr = {
|
296
|
-
'Connection' => 'Keep-Alive',
|
297
|
-
'Content-Type' =>
|
298
|
-
'multipart/encrypted;' \
|
299
|
-
'protocol="application/HTTP-Kerberos-session-encrypted";' \
|
300
|
-
'boundary="Encrypted Boundary"'
|
301
|
-
}
|
302
|
-
body = [
|
303
|
-
"--Encrypted Boundary",
|
304
|
-
"Content-Type: application/HTTP-Kerberos-session-encrypted",
|
305
|
-
"OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length + pad_len}",
|
306
|
-
"--Encrypted Boundary",
|
307
|
-
"Content-Type: application/octet-stream",
|
308
|
-
"#{emsg}--Encrypted Boundary--",
|
309
|
-
""
|
310
|
-
].join("\r\n")
|
311
|
-
|
312
|
-
resp = @httpcli.post(@endpoint, body, hdr)
|
313
|
-
log_soap_message(resp.http_body.content)
|
314
|
-
resp
|
315
|
-
end
|
316
|
-
|
317
|
-
def init_krb
|
318
|
-
@logger.debug "Initializing Kerberos for #{@service}"
|
319
|
-
@gsscli = GSSAPI::Simple.new(@endpoint.host, @service)
|
320
|
-
token = @gsscli.init_context
|
321
|
-
auth = Base64.strict_encode64 token
|
322
|
-
|
323
|
-
hdr = {
|
324
|
-
'Authorization' => "Kerberos #{auth}",
|
325
|
-
'Connection' => 'Keep-Alive',
|
326
|
-
'Content-Type' => 'application/soap+xml;charset=UTF-8'
|
327
|
-
}
|
328
|
-
@logger.debug 'Sending HTTP POST for Kerberos Authentication'
|
329
|
-
r = @httpcli.post(@endpoint, '', hdr)
|
330
|
-
itok = r.header['WWW-Authenticate'].pop
|
331
|
-
itok = itok.split.last
|
332
|
-
itok = Base64.strict_decode64(itok)
|
333
|
-
@gsscli.init_context(itok)
|
334
|
-
end
|
335
|
-
|
336
|
-
# @return [String] the encrypted request string
|
337
|
-
def winrm_encrypt(str)
|
338
|
-
@logger.debug "Encrypting SOAP message:\n#{str}"
|
339
|
-
iov_cnt = 3
|
340
|
-
iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
|
341
|
-
|
342
|
-
iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
|
343
|
-
iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
|
344
|
-
GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
|
345
|
-
|
346
|
-
iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
|
347
|
-
FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1)))
|
348
|
-
iov1[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA)
|
349
|
-
iov1[:buffer].value = str
|
350
|
-
|
351
|
-
iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
|
352
|
-
FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2)))
|
353
|
-
iov2[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_PADDING | \
|
354
|
-
GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
|
355
|
-
|
356
|
-
conf_state = FFI::MemoryPointer.new :uint32
|
357
|
-
min_stat = FFI::MemoryPointer.new :uint32
|
358
|
-
|
359
|
-
GSSAPI::LibGSSAPI.gss_wrap_iov(
|
360
|
-
min_stat,
|
361
|
-
@gsscli.context,
|
362
|
-
1,
|
363
|
-
GSSAPI::LibGSSAPI::GSS_C_QOP_DEFAULT,
|
364
|
-
conf_state,
|
365
|
-
iov,
|
366
|
-
iov_cnt)
|
367
|
-
|
368
|
-
token = [iov0[:buffer].length].pack('L')
|
369
|
-
token += iov0[:buffer].value
|
370
|
-
token += iov1[:buffer].value
|
371
|
-
pad_len = iov2[:buffer].length
|
372
|
-
token += iov2[:buffer].value if pad_len > 0
|
373
|
-
[pad_len, token]
|
374
|
-
end
|
375
|
-
|
376
|
-
# @return [String] the unencrypted response string
|
377
|
-
def winrm_decrypt(str)
|
378
|
-
@logger.debug "Decrypting SOAP message:\n#{str}"
|
379
|
-
iov_cnt = 3
|
380
|
-
iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
|
381
|
-
|
382
|
-
iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
|
383
|
-
iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
|
384
|
-
GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
|
385
|
-
|
386
|
-
iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
|
387
|
-
FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1)))
|
388
|
-
iov1[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA)
|
389
|
-
|
390
|
-
iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
|
391
|
-
FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2)))
|
392
|
-
iov2[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA)
|
393
|
-
|
394
|
-
str.force_encoding('BINARY')
|
395
|
-
str.sub!(/^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$/m, '\1')
|
396
|
-
|
397
|
-
len = str.unpack('L').first
|
398
|
-
iov_data = str.unpack("LA#{len}A*")
|
399
|
-
iov0[:buffer].value = iov_data[1]
|
400
|
-
iov1[:buffer].value = iov_data[2]
|
401
|
-
|
402
|
-
min_stat = FFI::MemoryPointer.new :uint32
|
403
|
-
conf_state = FFI::MemoryPointer.new :uint32
|
404
|
-
conf_state.write_int(1)
|
405
|
-
qop_state = FFI::MemoryPointer.new :uint32
|
406
|
-
qop_state.write_int(0)
|
407
|
-
|
408
|
-
maj_stat = GSSAPI::LibGSSAPI.gss_unwrap_iov(
|
409
|
-
min_stat, @gsscli.context, conf_state, qop_state, iov, iov_cnt)
|
410
|
-
|
411
|
-
@logger.debug "SOAP message decrypted (MAJ: #{maj_stat}, " \
|
412
|
-
"MIN: #{min_stat.read_int}):\n#{iov1[:buffer].value}"
|
413
|
-
|
414
|
-
iov1[:buffer].value
|
415
|
-
end
|
416
|
-
# rubocop:enable Metrics/MethodLength
|
417
|
-
# rubocop:enable Metrics/AbcSize
|
418
|
-
end
|
419
|
-
# rubocop:enable Metrics/ClassLength
|
420
|
-
end
|
421
|
-
end # WinRM
|
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_relative 'response_handler'
|
18
|
+
|
19
|
+
module WinRM
|
20
|
+
module HTTP
|
21
|
+
# A generic HTTP transport that utilized HTTPClient to send messages back and forth.
|
22
|
+
# This backend will maintain state for every WinRMWebService instance that is instantiated so it
|
23
|
+
# is possible to use GSSAPI with Keep-Alive.
|
24
|
+
class HttpTransport
|
25
|
+
# Set this to an unreasonable amount because WinRM has its own timeouts
|
26
|
+
DEFAULT_RECEIVE_TIMEOUT = 3600
|
27
|
+
|
28
|
+
attr_reader :endpoint
|
29
|
+
|
30
|
+
def initialize(endpoint)
|
31
|
+
@endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
|
32
|
+
@httpcli = HTTPClient.new(agent_name: 'Ruby WinRM Client')
|
33
|
+
@httpcli.receive_timeout = DEFAULT_RECEIVE_TIMEOUT
|
34
|
+
@logger = Logging.logger[self]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sends the SOAP payload to the WinRM service and returns the service's
|
38
|
+
# SOAP response. If an error occurrs an appropriate error is raised.
|
39
|
+
#
|
40
|
+
# @param [String] The XML SOAP message
|
41
|
+
# @returns [REXML::Document] The parsed response body
|
42
|
+
def send_request(message)
|
43
|
+
ssl_peer_fingerprint_verification!
|
44
|
+
log_soap_message(message)
|
45
|
+
hdr = { 'Content-Type' => 'application/soap+xml;charset=UTF-8',
|
46
|
+
'Content-Length' => message.length }
|
47
|
+
resp = @httpcli.post(@endpoint, message, hdr)
|
48
|
+
log_soap_message(resp.http_body.content)
|
49
|
+
verify_ssl_fingerprint(resp.peer_cert)
|
50
|
+
handler = WinRM::ResponseHandler.new(resp.http_body.content, resp.status)
|
51
|
+
handler.parse_to_xml
|
52
|
+
end
|
53
|
+
|
54
|
+
# We'll need this to force basic authentication if desired
|
55
|
+
def basic_auth_only!
|
56
|
+
auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
57
|
+
auths.delete_if { |i| i.scheme !~ /basic/i }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Disable SSPI Auth
|
61
|
+
def no_sspi_auth!
|
62
|
+
auths = @httpcli.www_auth.instance_variable_get('@authenticator')
|
63
|
+
auths.delete_if { |i| i.is_a? HTTPClient::SSPINegotiateAuth }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Disable SSL Peer Verification
|
67
|
+
def no_ssl_peer_verification!
|
68
|
+
@httpcli.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
69
|
+
end
|
70
|
+
|
71
|
+
# SSL Peer Fingerprint Verification prior to connecting
|
72
|
+
def ssl_peer_fingerprint_verification!
|
73
|
+
return unless @ssl_peer_fingerprint && ! @ssl_peer_fingerprint_verified
|
74
|
+
|
75
|
+
with_untrusted_ssl_connection do |connection|
|
76
|
+
connection_cert = connection.peer_cert_chain.last
|
77
|
+
verify_ssl_fingerprint(connection_cert)
|
78
|
+
end
|
79
|
+
@logger.info("initial ssl fingerprint #{@ssl_peer_fingerprint} verified\n")
|
80
|
+
@ssl_peer_fingerprint_verified = true
|
81
|
+
no_ssl_peer_verification!
|
82
|
+
end
|
83
|
+
|
84
|
+
# Connect without verification to retrieve untrusted ssl context
|
85
|
+
def with_untrusted_ssl_connection
|
86
|
+
noverify_peer_context = OpenSSL::SSL::SSLContext.new
|
87
|
+
noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
88
|
+
tcp_connection = TCPSocket.new(@endpoint.host, @endpoint.port)
|
89
|
+
begin
|
90
|
+
ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_context)
|
91
|
+
ssl_connection.connect
|
92
|
+
yield ssl_connection
|
93
|
+
ensure
|
94
|
+
tcp_connection.close
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# compare @ssl_peer_fingerprint to current ssl context
|
99
|
+
def verify_ssl_fingerprint(cert)
|
100
|
+
return unless @ssl_peer_fingerprint
|
101
|
+
conn_fingerprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
|
102
|
+
return unless @ssl_peer_fingerprint.casecmp(conn_fingerprint) != 0
|
103
|
+
fail "ssl fingerprint mismatch!!!!\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
# HTTP Client receive timeout. How long should a remote call wait for a
|
107
|
+
# for a response from WinRM?
|
108
|
+
def receive_timeout=(sec)
|
109
|
+
@httpcli.receive_timeout = sec
|
110
|
+
end
|
111
|
+
|
112
|
+
def receive_timeout
|
113
|
+
@httpcli.receive_timeout
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
|
118
|
+
def log_soap_message(message)
|
119
|
+
return unless @logger.debug?
|
120
|
+
|
121
|
+
xml_msg = REXML::Document.new(message)
|
122
|
+
formatter = REXML::Formatters::Pretty.new(2)
|
123
|
+
formatter.compact = true
|
124
|
+
formatter.write(xml_msg, @logger)
|
125
|
+
@logger.debug("\n")
|
126
|
+
rescue StandardError => e
|
127
|
+
@logger.debug("Couldn't log SOAP request/response: #{e.message} - #{message}")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
# Plain text, insecure, HTTP transport
|
133
|
+
class HttpPlaintext < HttpTransport
|
134
|
+
def initialize(endpoint, user, pass, opts)
|
135
|
+
super(endpoint)
|
136
|
+
@httpcli.set_auth(nil, user, pass)
|
137
|
+
no_sspi_auth! if opts[:disable_sspi]
|
138
|
+
basic_auth_only! if opts[:basic_auth_only]
|
139
|
+
no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# NTLM/Negotiate, secure, HTTP transport
|
145
|
+
class HttpNegotiate < HttpTransport
|
146
|
+
def initialize(endpoint, user, pass, opts)
|
147
|
+
super(endpoint)
|
148
|
+
require 'rubyntlm'
|
149
|
+
no_sspi_auth!
|
150
|
+
|
151
|
+
user_parts = user.split('\\')
|
152
|
+
if(user_parts.length > 1)
|
153
|
+
opts[:domain] = user_parts[0]
|
154
|
+
user = user_parts[1]
|
155
|
+
end
|
156
|
+
|
157
|
+
@ntlmcli = Net::NTLM::Client.new(user, pass, opts)
|
158
|
+
@retryable = true
|
159
|
+
no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
|
160
|
+
@ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
|
161
|
+
@httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
|
162
|
+
end
|
163
|
+
|
164
|
+
def send_request(message, auth_header = nil)
|
165
|
+
ssl_peer_fingerprint_verification!
|
166
|
+
auth_header = init_auth if @ntlmcli.session.nil?
|
167
|
+
|
168
|
+
original_length = message.length
|
169
|
+
|
170
|
+
emessage = @ntlmcli.session.seal_message message
|
171
|
+
signature = @ntlmcli.session.sign_message message
|
172
|
+
seal = "\x10\x00\x00\x00#{signature}#{emessage}"
|
173
|
+
|
174
|
+
hdr = {
|
175
|
+
"Content-Type" => "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\""
|
176
|
+
}
|
177
|
+
hdr.merge!(auth_header) if auth_header
|
178
|
+
|
179
|
+
body = [
|
180
|
+
"--Encrypted Boundary",
|
181
|
+
"Content-Type: application/HTTP-SPNEGO-session-encrypted",
|
182
|
+
"OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length}",
|
183
|
+
"--Encrypted Boundary",
|
184
|
+
"Content-Type: application/octet-stream",
|
185
|
+
"#{seal}--Encrypted Boundary--",
|
186
|
+
""
|
187
|
+
].join("\r\n")
|
188
|
+
|
189
|
+
resp = @httpcli.post(@endpoint, body, hdr)
|
190
|
+
verify_ssl_fingerprint(resp.peer_cert)
|
191
|
+
if resp.status == 401 && @retryable
|
192
|
+
@retryable = false
|
193
|
+
send_request(message, init_auth)
|
194
|
+
else
|
195
|
+
@retryable = true
|
196
|
+
decrypted_body = resp.body.empty? ? '' : winrm_decrypt(resp.body)
|
197
|
+
handler = WinRM::ResponseHandler.new(decrypted_body, resp.status)
|
198
|
+
handler.parse_to_xml()
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def winrm_decrypt(str)
|
205
|
+
str.force_encoding('BINARY')
|
206
|
+
str.sub!(/^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$/m, '\1')
|
207
|
+
|
208
|
+
signature = str[4..19]
|
209
|
+
message = @ntlmcli.session.unseal_message str[20..-1]
|
210
|
+
if @ntlmcli.session.verify_signature(signature, message)
|
211
|
+
message
|
212
|
+
else
|
213
|
+
raise WinRMWebServiceError, "Could not verify SOAP message."
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def init_auth
|
218
|
+
@logger.debug "Initializing Negotiate for #{@service}"
|
219
|
+
auth1 = @ntlmcli.init_context
|
220
|
+
hdr = {"Authorization" => "Negotiate #{auth1.encode64}",
|
221
|
+
"Content-Type" => "application/soap+xml;charset=UTF-8"
|
222
|
+
}
|
223
|
+
@logger.debug "Sending HTTP POST for Negotiate Authentication"
|
224
|
+
r = @httpcli.post(@endpoint, "", hdr)
|
225
|
+
verify_ssl_fingerprint(r.peer_cert)
|
226
|
+
itok = r.header["WWW-Authenticate"].pop.split.last
|
227
|
+
binding = r.peer_cert.nil? ? nil : Net::NTLM::ChannelBinding.create(r.peer_cert)
|
228
|
+
auth3 = @ntlmcli.init_context(itok, binding)
|
229
|
+
{ "Authorization" => "Negotiate #{auth3.encode64}" }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Uses SSL to secure the transport
|
234
|
+
class BasicAuthSSL < HttpTransport
|
235
|
+
def initialize(endpoint, user, pass, opts)
|
236
|
+
super(endpoint)
|
237
|
+
@httpcli.set_auth(endpoint, user, pass)
|
238
|
+
basic_auth_only!
|
239
|
+
no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
|
240
|
+
@ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
|
241
|
+
@httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Uses Kerberos/GSSAPI to authenticate and encrypt messages
|
246
|
+
# rubocop:disable Metrics/ClassLength
|
247
|
+
class HttpGSSAPI < HttpTransport
|
248
|
+
# @param [String,URI] endpoint the WinRM webservice endpoint
|
249
|
+
# @param [String] realm the Kerberos realm we are authenticating to
|
250
|
+
# @param [String<optional>] service the service name, default is HTTP
|
251
|
+
# @param [String<optional>] keytab the path to a keytab file if you are using one
|
252
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
253
|
+
def initialize(endpoint, realm, service = nil, keytab = nil, opts)
|
254
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
255
|
+
super(endpoint)
|
256
|
+
# Remove the GSSAPI auth from HTTPClient because we are doing our own thing
|
257
|
+
no_sspi_auth!
|
258
|
+
service ||= 'HTTP'
|
259
|
+
@service = "#{service}/#{@endpoint.host}@#{realm}"
|
260
|
+
init_krb
|
261
|
+
end
|
262
|
+
|
263
|
+
# Sends the SOAP payload to the WinRM service and returns the service's
|
264
|
+
# SOAP response. If an error occurrs an appropriate error is raised.
|
265
|
+
#
|
266
|
+
# @param [String] The XML SOAP message
|
267
|
+
# @returns [REXML::Document] The parsed response body
|
268
|
+
def send_request(message)
|
269
|
+
resp = send_kerberos_request(message)
|
270
|
+
|
271
|
+
if resp.status == 401
|
272
|
+
@logger.debug 'Got 401 - reinitializing Kerberos and retrying one more time'
|
273
|
+
init_krb
|
274
|
+
resp = send_kerberos_request(message)
|
275
|
+
end
|
276
|
+
|
277
|
+
handler = WinRM::ResponseHandler.new(winrm_decrypt(resp.http_body.content), resp.status)
|
278
|
+
handler.parse_to_xml
|
279
|
+
end
|
280
|
+
|
281
|
+
private
|
282
|
+
|
283
|
+
# rubocop:disable Metrics/MethodLength
|
284
|
+
# rubocop:disable Metrics/AbcSize
|
285
|
+
|
286
|
+
# Sends the SOAP payload to the WinRM service and returns the service's
|
287
|
+
# HTTP response.
|
288
|
+
#
|
289
|
+
# @param [String] The XML SOAP message
|
290
|
+
# @returns [Object] The HTTP response object
|
291
|
+
def send_kerberos_request(message)
|
292
|
+
log_soap_message(message)
|
293
|
+
original_length = message.length
|
294
|
+
pad_len, emsg = winrm_encrypt(message)
|
295
|
+
hdr = {
|
296
|
+
'Connection' => 'Keep-Alive',
|
297
|
+
'Content-Type' =>
|
298
|
+
'multipart/encrypted;' \
|
299
|
+
'protocol="application/HTTP-Kerberos-session-encrypted";' \
|
300
|
+
'boundary="Encrypted Boundary"'
|
301
|
+
}
|
302
|
+
body = [
|
303
|
+
"--Encrypted Boundary",
|
304
|
+
"Content-Type: application/HTTP-Kerberos-session-encrypted",
|
305
|
+
"OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length + pad_len}",
|
306
|
+
"--Encrypted Boundary",
|
307
|
+
"Content-Type: application/octet-stream",
|
308
|
+
"#{emsg}--Encrypted Boundary--",
|
309
|
+
""
|
310
|
+
].join("\r\n")
|
311
|
+
|
312
|
+
resp = @httpcli.post(@endpoint, body, hdr)
|
313
|
+
log_soap_message(resp.http_body.content)
|
314
|
+
resp
|
315
|
+
end
|
316
|
+
|
317
|
+
def init_krb
|
318
|
+
@logger.debug "Initializing Kerberos for #{@service}"
|
319
|
+
@gsscli = GSSAPI::Simple.new(@endpoint.host, @service)
|
320
|
+
token = @gsscli.init_context
|
321
|
+
auth = Base64.strict_encode64 token
|
322
|
+
|
323
|
+
hdr = {
|
324
|
+
'Authorization' => "Kerberos #{auth}",
|
325
|
+
'Connection' => 'Keep-Alive',
|
326
|
+
'Content-Type' => 'application/soap+xml;charset=UTF-8'
|
327
|
+
}
|
328
|
+
@logger.debug 'Sending HTTP POST for Kerberos Authentication'
|
329
|
+
r = @httpcli.post(@endpoint, '', hdr)
|
330
|
+
itok = r.header['WWW-Authenticate'].pop
|
331
|
+
itok = itok.split.last
|
332
|
+
itok = Base64.strict_decode64(itok)
|
333
|
+
@gsscli.init_context(itok)
|
334
|
+
end
|
335
|
+
|
336
|
+
# @return [String] the encrypted request string
|
337
|
+
def winrm_encrypt(str)
|
338
|
+
@logger.debug "Encrypting SOAP message:\n#{str}"
|
339
|
+
iov_cnt = 3
|
340
|
+
iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
|
341
|
+
|
342
|
+
iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
|
343
|
+
iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
|
344
|
+
GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
|
345
|
+
|
346
|
+
iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
|
347
|
+
FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1)))
|
348
|
+
iov1[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA)
|
349
|
+
iov1[:buffer].value = str
|
350
|
+
|
351
|
+
iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
|
352
|
+
FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2)))
|
353
|
+
iov2[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_PADDING | \
|
354
|
+
GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
|
355
|
+
|
356
|
+
conf_state = FFI::MemoryPointer.new :uint32
|
357
|
+
min_stat = FFI::MemoryPointer.new :uint32
|
358
|
+
|
359
|
+
GSSAPI::LibGSSAPI.gss_wrap_iov(
|
360
|
+
min_stat,
|
361
|
+
@gsscli.context,
|
362
|
+
1,
|
363
|
+
GSSAPI::LibGSSAPI::GSS_C_QOP_DEFAULT,
|
364
|
+
conf_state,
|
365
|
+
iov,
|
366
|
+
iov_cnt)
|
367
|
+
|
368
|
+
token = [iov0[:buffer].length].pack('L')
|
369
|
+
token += iov0[:buffer].value
|
370
|
+
token += iov1[:buffer].value
|
371
|
+
pad_len = iov2[:buffer].length
|
372
|
+
token += iov2[:buffer].value if pad_len > 0
|
373
|
+
[pad_len, token]
|
374
|
+
end
|
375
|
+
|
376
|
+
# @return [String] the unencrypted response string
|
377
|
+
def winrm_decrypt(str)
|
378
|
+
@logger.debug "Decrypting SOAP message:\n#{str}"
|
379
|
+
iov_cnt = 3
|
380
|
+
iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
|
381
|
+
|
382
|
+
iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
|
383
|
+
iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
|
384
|
+
GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
|
385
|
+
|
386
|
+
iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
|
387
|
+
FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1)))
|
388
|
+
iov1[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA)
|
389
|
+
|
390
|
+
iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
|
391
|
+
FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2)))
|
392
|
+
iov2[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA)
|
393
|
+
|
394
|
+
str.force_encoding('BINARY')
|
395
|
+
str.sub!(/^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$/m, '\1')
|
396
|
+
|
397
|
+
len = str.unpack('L').first
|
398
|
+
iov_data = str.unpack("LA#{len}A*")
|
399
|
+
iov0[:buffer].value = iov_data[1]
|
400
|
+
iov1[:buffer].value = iov_data[2]
|
401
|
+
|
402
|
+
min_stat = FFI::MemoryPointer.new :uint32
|
403
|
+
conf_state = FFI::MemoryPointer.new :uint32
|
404
|
+
conf_state.write_int(1)
|
405
|
+
qop_state = FFI::MemoryPointer.new :uint32
|
406
|
+
qop_state.write_int(0)
|
407
|
+
|
408
|
+
maj_stat = GSSAPI::LibGSSAPI.gss_unwrap_iov(
|
409
|
+
min_stat, @gsscli.context, conf_state, qop_state, iov, iov_cnt)
|
410
|
+
|
411
|
+
@logger.debug "SOAP message decrypted (MAJ: #{maj_stat}, " \
|
412
|
+
"MIN: #{min_stat.read_int}):\n#{iov1[:buffer].value}"
|
413
|
+
|
414
|
+
iov1[:buffer].value
|
415
|
+
end
|
416
|
+
# rubocop:enable Metrics/MethodLength
|
417
|
+
# rubocop:enable Metrics/AbcSize
|
418
|
+
end
|
419
|
+
# rubocop:enable Metrics/ClassLength
|
420
|
+
end
|
421
|
+
end # WinRM
|