class WinRM::HTTP::HttpGSSAPI

Uses Kerberos/GSSAPI to authenticate and encrypt messages

Public Class Methods

new(endpoint, realm, opts, service = nil) click to toggle source

@param [String,URI] endpoint the WinRM webservice endpoint @param [String] realm the Kerberos realm we are authenticating to @param [String<optional>] service the service name, default is HTTP

Calls superclass method WinRM::HTTP::HttpTransport::new
# File lib/winrm/http/transport.rb, line 291
def initialize(endpoint, realm, opts, service = nil)
  require 'gssapi'
  require 'gssapi/extensions'

  super(endpoint, opts)
  # Remove the GSSAPI auth from HTTPClient because we are doing our own thing
  no_sspi_auth!
  service ||= 'HTTP'
  @service = "#{service}/#{@endpoint.host}@#{realm}"
  no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
  init_krb
end

Public Instance Methods

send_request(message) click to toggle source

Sends the SOAP payload to the WinRM service and returns the service’s SOAP response. If an error occurrs an appropriate error is raised.

@param [String] The XML SOAP message @returns [REXML::Document] The parsed response body

# File lib/winrm/http/transport.rb, line 309
def send_request(message)
  resp = send_kerberos_request(message)

  if resp.status == 401
    @logger.debug 'Got 401 - reinitializing Kerberos and retrying one more time'
    init_krb
    resp = send_kerberos_request(message)
  end

  handler = WinRM::ResponseHandler.new(winrm_decrypt(resp.http_body.content), resp.status)
  handler.parse_to_xml
end

Private Instance Methods

init_krb() click to toggle source
# File lib/winrm/http/transport.rb, line 349
def init_krb
  @logger.debug "Initializing Kerberos for #{@service}"
  @gsscli = GSSAPI::Simple.new(@endpoint.host, @service)
  token = @gsscli.init_context
  auth = Base64.strict_encode64 token

  hdr = {
    'Authorization' => "Kerberos #{auth}",
    'Connection' => 'Keep-Alive',
    'Content-Type' => 'application/soap+xml;charset=UTF-8'
  }
  @logger.debug 'Sending HTTP POST for Kerberos Authentication'
  r = @httpcli.post(@endpoint, '', hdr)
  itok = r.header['WWW-Authenticate'].pop
  itok = itok.split.last
  itok = Base64.strict_decode64(itok)
  @gsscli.init_context(itok)
end
send_kerberos_request(message) click to toggle source

Sends the SOAP payload to the WinRM service and returns the service’s HTTP response.

@param [String] The XML SOAP message @returns [Object] The HTTP response object

# File lib/winrm/http/transport.rb, line 329
def send_kerberos_request(message)
  log_soap_message(message)
  original_length = message.bytesize
  pad_len, emsg = winrm_encrypt(message)
  req_length = original_length + pad_len
  hdr = {
    'Connection' => 'Keep-Alive',
    'Content-Type' => 'multipart/encrypted;' \
      'protocol="application/HTTP-Kerberos-session-encrypted";boundary="Encrypted Boundary"'
  }

  resp = @httpcli.post(
    @endpoint,
    body(emsg, req_length, 'application/HTTP-Kerberos-session-encrypted'),
    hdr
  )
  log_soap_message(resp.http_body.content)
  resp
end
winrm_decrypt(str) click to toggle source

@return [String] the unencrypted response string

# File lib/winrm/http/transport.rb, line 415
def winrm_decrypt(str)
  @logger.debug "Decrypting SOAP message:\n#{str}"
  iov_cnt = 3
  iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)

  iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
  iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
    GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)

  iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
    FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1))
  )
  iov1[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA

  iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
    FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2))
  )
  iov2[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA

  str.force_encoding('BINARY')
  str.sub!(%r{^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$}m, '\1')

  len = str.unpack('L').first
  iov_data = str.unpack("La#{len}a*")
  iov0[:buffer].value = iov_data[1]
  iov1[:buffer].value = iov_data[2]

  min_stat = FFI::MemoryPointer.new :uint32
  conf_state = FFI::MemoryPointer.new :uint32
  conf_state.write_int(1)
  qop_state = FFI::MemoryPointer.new :uint32
  qop_state.write_int(0)

  maj_stat = GSSAPI::LibGSSAPI.gss_unwrap_iov(
    min_stat, @gsscli.context, conf_state, qop_state, iov, iov_cnt
  )

  @logger.debug "SOAP message decrypted (MAJ: #{maj_stat}, " \
    "MIN: #{min_stat.read_int}):\n#{iov1[:buffer].value}"

  iov1[:buffer].value
end
winrm_encrypt(str) click to toggle source

@return [String] the encrypted request string

# File lib/winrm/http/transport.rb, line 372
def winrm_encrypt(str)
  @logger.debug "Encrypting SOAP message:\n#{str}"
  iov_cnt = 3
  iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)

  iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
  iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
    GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)

  iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
    FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1))
  )
  iov1[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
  iov1[:buffer].value = str

  iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
    FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2))
  )
  iov2[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_PADDING | \
    GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)

  conf_state = FFI::MemoryPointer.new :uint32
  min_stat = FFI::MemoryPointer.new :uint32

  GSSAPI::LibGSSAPI.gss_wrap_iov(
    min_stat,
    @gsscli.context,
    1,
    GSSAPI::LibGSSAPI::GSS_C_QOP_DEFAULT,
    conf_state,
    iov,
    iov_cnt
  )

  token = [iov0[:buffer].length].pack('L')
  token += iov0[:buffer].value
  token += iov1[:buffer].value
  pad_len = iov2[:buffer].length
  token += iov2[:buffer].value if pad_len > 0
  [pad_len, token]
end