class WinRM::HTTP::HttpGSSAPI

Uses Kerberos/GSSAPI to authenticate and encrypt messages rubocop:disable Metrics/ClassLength

Public Class Methods

new(endpoint, realm, service = nil, keytab = nil, opts) 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 @param [String<optional>] keytab the path to a keytab file if you are using one rubocop:disable Lint/UnusedMethodArgument

Calls superclass method WinRM::HTTP::HttpTransport.new
# File lib/winrm/http/transport.rb, line 125
def initialize(endpoint, realm, service = nil, keytab = nil, opts)
  # rubocop:enable Lint/UnusedMethodArgument
  super(endpoint)
  # Remove the GSSAPI auth from HTTPClient because we are doing our own thing
  no_sspi_auth!
  service ||= 'HTTP'
  @service = "#{service}/#{@endpoint.host}@#{realm}"
  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 140
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 189
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 163
      def send_kerberos_request(message)
        log_soap_message(message)
        original_length = message.length
        pad_len, emsg = winrm_encrypt(message)
        hdr = {
          'Connection' => 'Keep-Alive',
          'Content-Type' =>
            'multipart/encrypted;'              'protocol="application/HTTP-Kerberos-session-encrypted";'              'boundary="Encrypted Boundary"'
        }

        body = <<-EOF
--Encrypted Boundary\r
Content-Type: application/HTTP-Kerberos-session-encrypted\r
OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length + pad_len}\r
--Encrypted Boundary\r
Content-Type: application/octet-stream\r
#{emsg}--Encrypted Boundary\r
        EOF

        resp = @httpcli.post(@endpoint, body, 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 249
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!(/^.*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 209
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