win32-sspi 0.0.1.rc1-x86-mingw32
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 +7 -0
- data/License.txt +8 -0
- data/README.md +31 -0
- data/Rakefile +49 -0
- data/examples/sspi_negotiate_client.rb +42 -0
- data/examples/sspi_negotiate_server.rb +99 -0
- data/lib/win32-sspi.rb +2 -0
- data/lib/win32/sspi/api/client.rb +17 -0
- data/lib/win32/sspi/api/common.rb +133 -0
- data/lib/win32/sspi/api/server.rb +32 -0
- data/lib/win32/sspi/negotiate/client.rb +159 -0
- data/lib/win32/sspi/negotiate/server.rb +199 -0
- data/lib/win32/sspi/windows/constants.rb +36 -0
- data/lib/win32/sspi/windows/functions.rb +31 -0
- data/lib/win32/sspi/windows/misc.rb +24 -0
- data/lib/win32/sspi/windows/structs.rb +147 -0
- data/test/all_tests.rb +2 -0
- data/test/test_win32_sspi_negotiate_client.rb +417 -0
- data/test/test_win32_sspi_negotiate_server.rb +441 -0
- data/test/test_win32_sspi_structure_creates.rb +100 -0
- data/win32-sspi.gemspec +29 -0
- metadata +102 -0
| @@ -0,0 +1,199 @@ | |
| 1 | 
            +
            require_relative '../windows/constants'
         | 
| 2 | 
            +
            require_relative '../windows/misc'
         | 
| 3 | 
            +
            require_relative '../api/server'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Win32
         | 
| 6 | 
            +
              module SSPI
         | 
| 7 | 
            +
                module Negotiate
         | 
| 8 | 
            +
                  class Server
         | 
| 9 | 
            +
                    include Windows::Constants
         | 
| 10 | 
            +
                    include API::Server
         | 
| 11 | 
            +
                  
         | 
| 12 | 
            +
                    attr_accessor :auth_type
         | 
| 13 | 
            +
                    attr_reader :token
         | 
| 14 | 
            +
                    attr_reader :username
         | 
| 15 | 
            +
                    attr_reader :domain
         | 
| 16 | 
            +
                  
         | 
| 17 | 
            +
                    def initialize(options={})
         | 
| 18 | 
            +
                      @auth_type = options[:auth_type] || AUTH_TYPE_NEGOTIATE
         | 
| 19 | 
            +
                      @token = ""
         | 
| 20 | 
            +
                      @username = ''
         | 
| 21 | 
            +
                      @domain = ''
         | 
| 22 | 
            +
                      @credentials_handle = nil
         | 
| 23 | 
            +
                      @context_handle = nil
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                    
         | 
| 26 | 
            +
                    def http_authenticate(header,&block)
         | 
| 27 | 
            +
                      perform_authenticate(header,block,true)
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                    
         | 
| 30 | 
            +
                    def authenticate(client_msg,&block)
         | 
| 31 | 
            +
                      perform_authenticate(client_msg,block,false)
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                    
         | 
| 34 | 
            +
                    def perform_authenticate(client_msg,block,is_http_header)
         | 
| 35 | 
            +
                      authenticated = false
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      status = acquire_handle
         | 
| 38 | 
            +
                      if SEC_E_OK == status
         | 
| 39 | 
            +
                        token_from_client_message(client_msg,is_http_header)
         | 
| 40 | 
            +
                        status = accept_context(self.token)
         | 
| 41 | 
            +
                        if SEC_I_CONTINUE_NEEDED == status
         | 
| 42 | 
            +
                          block.call(block_arg_from_token(is_http_header),authenticated)
         | 
| 43 | 
            +
                          return authenticated
         | 
| 44 | 
            +
                        end
         | 
| 45 | 
            +
                        
         | 
| 46 | 
            +
                        if [SEC_I_COMPLETE_NEEDED, SEC_I_COMPLETE_AND_CONTINUE].include?(status)
         | 
| 47 | 
            +
                          status = complete_authentication
         | 
| 48 | 
            +
                        end
         | 
| 49 | 
            +
                        
         | 
| 50 | 
            +
                        if SEC_E_OK == status
         | 
| 51 | 
            +
                          authenticated = true
         | 
| 52 | 
            +
                          status = query_attributes
         | 
| 53 | 
            +
                          if SEC_E_OK == status
         | 
| 54 | 
            +
                            free_handles
         | 
| 55 | 
            +
                          end
         | 
| 56 | 
            +
                          
         | 
| 57 | 
            +
                          block.call(block_arg_from_token(is_http_header),authenticated)
         | 
| 58 | 
            +
                        end
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                      
         | 
| 61 | 
            +
                      return authenticated
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                    
         | 
| 64 | 
            +
                    def block_arg_from_token(is_http_header)
         | 
| 65 | 
            +
                      block_arg = nil
         | 
| 66 | 
            +
                      if self.token && self.token.length > 0
         | 
| 67 | 
            +
                        block_arg = is_http_header ? construct_http_header(self.auth_type,self.token) : self.token
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
                      block_arg
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
                    
         | 
| 72 | 
            +
                    def token_from_client_message(client_msg,is_http_header)
         | 
| 73 | 
            +
                      if client_msg
         | 
| 74 | 
            +
                        if is_http_header
         | 
| 75 | 
            +
                          @auth_type, @token = de_construct_http_header(client_msg)
         | 
| 76 | 
            +
                        else
         | 
| 77 | 
            +
                          @token = client_msg
         | 
| 78 | 
            +
                        end
         | 
| 79 | 
            +
                      end
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
                    
         | 
| 82 | 
            +
                    def acquire_handle
         | 
| 83 | 
            +
                      return SEC_E_OK if @credentials_handle
         | 
| 84 | 
            +
                    
         | 
| 85 | 
            +
                      @credentials_handle = create_credhandle
         | 
| 86 | 
            +
                      expiry = create_timestamp
         | 
| 87 | 
            +
                    
         | 
| 88 | 
            +
                      status = acquire_credentials_handle(
         | 
| 89 | 
            +
                        nil,
         | 
| 90 | 
            +
                        @auth_type,
         | 
| 91 | 
            +
                        SECPKG_CRED_INBOUND,
         | 
| 92 | 
            +
                        nil,
         | 
| 93 | 
            +
                        nil,
         | 
| 94 | 
            +
                        nil,
         | 
| 95 | 
            +
                        nil,
         | 
| 96 | 
            +
                        @credentials_handle,
         | 
| 97 | 
            +
                        expiry
         | 
| 98 | 
            +
                      )
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                      if SEC_E_OK != status
         | 
| 101 | 
            +
                        @credentials_handle = nil
         | 
| 102 | 
            +
                        raise SecurityStatusError.new('AcquireCredentialsHandle', status, FFI.errno)
         | 
| 103 | 
            +
                      end
         | 
| 104 | 
            +
                      
         | 
| 105 | 
            +
                      status
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                  
         | 
| 108 | 
            +
                    def accept_context(token=nil)
         | 
| 109 | 
            +
                      ctx = @context_handle
         | 
| 110 | 
            +
                      @context_handle ||= create_ctxhandle
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                      if token
         | 
| 113 | 
            +
                        input_buffer   = create_secbuffer(token)
         | 
| 114 | 
            +
                        input_buffer_desc  = create_secbufferdesc(input_buffer)
         | 
| 115 | 
            +
                      end
         | 
| 116 | 
            +
                      
         | 
| 117 | 
            +
                      rflags = ASC_REQ_CONFIDENTIALITY | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONNECTION
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                      output_buffer  = create_secbuffer
         | 
| 120 | 
            +
                      output_buffer_desc = create_secbufferdesc(output_buffer)
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                      context_attributes = FFI::MemoryPointer.new(:ulong)
         | 
| 123 | 
            +
                      expiry = create_timestamp
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                      status = accept_security_context(
         | 
| 126 | 
            +
                        @credentials_handle,
         | 
| 127 | 
            +
                        ctx,
         | 
| 128 | 
            +
                        (token ? input_buffer_desc : nil),
         | 
| 129 | 
            +
                        rflags,
         | 
| 130 | 
            +
                        SECURITY_NATIVE_DREP,
         | 
| 131 | 
            +
                        @context_handle,
         | 
| 132 | 
            +
                        output_buffer_desc,
         | 
| 133 | 
            +
                        context_attributes,
         | 
| 134 | 
            +
                        expiry
         | 
| 135 | 
            +
                      )
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                      a_success = [SEC_E_OK, SEC_I_CONTINUE_NEEDED, SEC_I_COMPLETE_NEEDED, SEC_I_COMPLETE_AND_CONTINUE]
         | 
| 138 | 
            +
                      if a_success.include?(status)
         | 
| 139 | 
            +
                        @token = output_buffer.to_ruby_s
         | 
| 140 | 
            +
                      else
         | 
| 141 | 
            +
                        raise SecurityStatusError.new('AcceptSecurityContext', status, FFI.errno)
         | 
| 142 | 
            +
                      end
         | 
| 143 | 
            +
                      
         | 
| 144 | 
            +
                      status
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
                    
         | 
| 147 | 
            +
                    def complete_authentication
         | 
| 148 | 
            +
                      status = SEC_E_OK
         | 
| 149 | 
            +
                      
         | 
| 150 | 
            +
                      if @token
         | 
| 151 | 
            +
                        input_buffer = create_secbuffer(@token)
         | 
| 152 | 
            +
                        input_buffer_desc  = create_secbufferdesc(input_buffer)
         | 
| 153 | 
            +
                        
         | 
| 154 | 
            +
                        status = complete_auth_token(@context_handle, input_buffer_desc)
         | 
| 155 | 
            +
                        if SEC_E_OK != status
         | 
| 156 | 
            +
                          raise SecurityStatusError.new('CompleteAuthToken', status, FFI.errno)
         | 
| 157 | 
            +
                        end
         | 
| 158 | 
            +
                      end
         | 
| 159 | 
            +
                      
         | 
| 160 | 
            +
                      status
         | 
| 161 | 
            +
                    end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                    def query_attributes
         | 
| 164 | 
            +
                      # Finally, let's get the user and domain
         | 
| 165 | 
            +
                      ptr = create_secpkg_context_names
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                      status = query_context_attributes(@context_handle, SECPKG_ATTR_NAMES, ptr)
         | 
| 168 | 
            +
                      if SEC_E_OK != status
         | 
| 169 | 
            +
                        raise SecurityStatusError.new('QueryContextAttributes', status, FFI.errno)
         | 
| 170 | 
            +
                      end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                      @username = ptr.to_ruby_s
         | 
| 173 | 
            +
                      if @username.include?("\\")
         | 
| 174 | 
            +
                        @domain, @username = @username.split("\\")
         | 
| 175 | 
            +
                      end
         | 
| 176 | 
            +
                      
         | 
| 177 | 
            +
                      status = free_context_buffer(ptr.to_username_ptr)
         | 
| 178 | 
            +
                      if SEC_E_OK != status
         | 
| 179 | 
            +
                        raise SecurityStatusError.new('FreeContextBuffer', status, FFI.errno)
         | 
| 180 | 
            +
                      end
         | 
| 181 | 
            +
                      
         | 
| 182 | 
            +
                      status
         | 
| 183 | 
            +
                    end
         | 
| 184 | 
            +
                    
         | 
| 185 | 
            +
                    def free_handles
         | 
| 186 | 
            +
                      result = free_context_and_credentials(@context_handle, @credentials_handle)
         | 
| 187 | 
            +
                      @context_handle, @credentials_handle = [nil,nil]
         | 
| 188 | 
            +
                      
         | 
| 189 | 
            +
                      if SEC_E_OK != result[:status]
         | 
| 190 | 
            +
                        raise SecurityStatusError.new(result[:name], result[:status], FFI.errno)
         | 
| 191 | 
            +
                      end
         | 
| 192 | 
            +
                      
         | 
| 193 | 
            +
                      result[:status]
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
                  
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
              end
         | 
| 199 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            module Windows
         | 
| 2 | 
            +
              module Constants
         | 
| 3 | 
            +
                SECURITY_NATIVE_DREP              = 0x00000010
         | 
| 4 | 
            +
                SECURITY_NETWORK_DREP             = 0x00000000
         | 
| 5 | 
            +
                SEC_WINNT_AUTH_IDENTITY_ANSI      = 1
         | 
| 6 | 
            +
                SEC_WINNT_AUTH_IDENTITY_UNICODE   = 2
         | 
| 7 | 
            +
                SECBUFFER_TOKEN                   = 2
         | 
| 8 | 
            +
                SECBUFFER_VERSION                 = 0
         | 
| 9 | 
            +
                TOKENBUFSIZE                      = 4096
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                SECPKG_ATTR_NAMES                 = 1
         | 
| 12 | 
            +
                SECPKG_CRED_INBOUND               = 0x00000001
         | 
| 13 | 
            +
                SECPKG_CRED_OUTBOUND              = 0x00000002
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                ISC_REQ_CONFIDENTIALITY           = 0x00000010
         | 
| 16 | 
            +
                ISC_REQ_REPLAY_DETECT             = 0x00000004
         | 
| 17 | 
            +
                ISC_REQ_CONNECTION                = 0x00000800
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                ASC_REQ_DELEGATE                  = 0x00000001
         | 
| 20 | 
            +
                ASC_REQ_MUTUAL_AUTH               = 0x00000002
         | 
| 21 | 
            +
                ASC_REQ_REPLAY_DETECT             = 0x00000004
         | 
| 22 | 
            +
                ASC_REQ_SEQUENCE_DETECT           = 0x00000008
         | 
| 23 | 
            +
                ASC_REQ_CONFIDENTIALITY           = 0x00000010
         | 
| 24 | 
            +
                ASC_REQ_CONNECTION                = 0x00000800
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                SEC_E_OK                          = 0x00000000
         | 
| 27 | 
            +
                SEC_I_CONTINUE_NEEDED             = 0x00090312
         | 
| 28 | 
            +
                SEC_I_COMPLETE_NEEDED             = 0x00090313
         | 
| 29 | 
            +
                SEC_I_COMPLETE_AND_CONTINUE       = 0x00090314
         | 
| 30 | 
            +
                SEC_E_INVALID_HANDLE              = 0x80090301
         | 
| 31 | 
            +
                SEC_E_INVALID_TOKEN               = 0x80090308
         | 
| 32 | 
            +
                SEC_E_LOGON_DENIED                = 0x8009030C
         | 
| 33 | 
            +
                SEC_E_SECPKG_NOT_FOUND            = 0x80090305
         | 
| 34 | 
            +
                SEC_E_WRONG_PRINCIPAL             = 0x80090322
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            require 'ffi'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Windows
         | 
| 4 | 
            +
              module Functions
         | 
| 5 | 
            +
                extend FFI::Library
         | 
| 6 | 
            +
                ffi_lib :secur32
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                attach_function :AcquireCredentialsHandle, :AcquireCredentialsHandleA,
         | 
| 9 | 
            +
                  [:string, :string, :ulong, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer],
         | 
| 10 | 
            +
                  :ulong
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                attach_function :InitializeSecurityContext, :InitializeSecurityContextA,
         | 
| 13 | 
            +
                  [:pointer, :pointer, :string, :ulong, :ulong, :ulong, :pointer, :ulong, :pointer, :pointer, :pointer, :pointer],
         | 
| 14 | 
            +
                  :ulong
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                attach_function :AcceptSecurityContext,
         | 
| 17 | 
            +
                  [:pointer, :pointer, :pointer, :ulong, :ulong, :pointer, :pointer, :pointer, :pointer],
         | 
| 18 | 
            +
                  :ulong
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                attach_function :CompleteAuthToken, [:pointer, :pointer], :ulong
         | 
| 21 | 
            +
                attach_function :DeleteSecurityContext, [:pointer], :ulong
         | 
| 22 | 
            +
                attach_function :EnumerateSecurityPackages, :EnumerateSecurityPackagesA, [:pointer, :pointer], :ulong
         | 
| 23 | 
            +
                attach_function :FreeContextBuffer, [:pointer], :ulong
         | 
| 24 | 
            +
                attach_function :FreeCredentialsHandle, [:pointer], :ulong
         | 
| 25 | 
            +
                attach_function :ImpersonateSecurityContext, [:pointer], :ulong
         | 
| 26 | 
            +
                attach_function :QueryContextAttributes, :QueryContextAttributesA, [:pointer, :ulong, :pointer], :ulong
         | 
| 27 | 
            +
                attach_function :QuerySecurityContextToken, [:pointer, :pointer], :ulong
         | 
| 28 | 
            +
                attach_function :QuerySecurityPackageInfo, :QuerySecurityPackageInfoA, [:string, :pointer], :ulong
         | 
| 29 | 
            +
                attach_function :RevertSecurityContext, [:pointer], :ulong
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            require 'ffi'
         | 
| 2 | 
            +
            require_relative 'constants'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class SecurityStatusError < StandardError
         | 
| 5 | 
            +
              extend FFI::Library
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              FORMAT_MESSAGE_FROM_SYSTEM_TABLE = 0x00001000
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              ffi_lib :kernel32
         | 
| 10 | 
            +
              attach_function :FormatMessageA, [:ulong, :ulong, :ulong, :ulong, :pointer, :ulong, :pointer], :ulong
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def initialize(context,status,errno)
         | 
| 13 | 
            +
                hex_status = '0x%X' % status
         | 
| 14 | 
            +
                msg = get_return_status_message(status)
         | 
| 15 | 
            +
                super("#{context}:\nffi_errno:#{errno} win32_status:#{hex_status}\nwin32 message:#{msg}")
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def get_return_status_message(win32_return_status)
         | 
| 19 | 
            +
                buf = FFI::MemoryPointer.new(:char, 512)
         | 
| 20 | 
            +
                flags = FORMAT_MESSAGE_FROM_SYSTEM_TABLE
         | 
| 21 | 
            +
                FormatMessageA(flags, 0, win32_return_status, 0, buf, buf.size, nil)
         | 
| 22 | 
            +
                buf.read_string
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,147 @@ | |
| 1 | 
            +
            require 'ffi'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Windows
         | 
| 4 | 
            +
              module Structs
         | 
| 5 | 
            +
                extend FFI::Library
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                class SEC_WINNT_AUTH_IDENTITY < FFI::Struct
         | 
| 8 | 
            +
                  layout(
         | 
| 9 | 
            +
                    :User, :pointer,
         | 
| 10 | 
            +
                    :UserLength, :ulong,
         | 
| 11 | 
            +
                    :Domain, :pointer,
         | 
| 12 | 
            +
                    :DomainLength, :ulong,
         | 
| 13 | 
            +
                    :Password, :pointer,
         | 
| 14 | 
            +
                    :PasswordLength, :ulong,
         | 
| 15 | 
            +
                    :Flags, :ulong
         | 
| 16 | 
            +
                  )
         | 
| 17 | 
            +
                  
         | 
| 18 | 
            +
                  def user_to_ruby_s
         | 
| 19 | 
            +
                    bsize = self[:UserLength]
         | 
| 20 | 
            +
                    bsize > 0 ? self[:User].read_string_length(bsize) : nil
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                  
         | 
| 23 | 
            +
                  def domain_to_ruby_s
         | 
| 24 | 
            +
                    bsize = self[:DomainLength]
         | 
| 25 | 
            +
                    bsize > 0 ? self[:Domain].read_string_length(bsize) : nil
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                  
         | 
| 28 | 
            +
                  def password_to_ruby_s
         | 
| 29 | 
            +
                    bsize = self[:PasswordLength]
         | 
| 30 | 
            +
                    bsize > 0 ? self[:Password].read_string_length(bsize) : nil
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                class SecHandle < FFI::Struct
         | 
| 35 | 
            +
                  layout(:dwLower, :pointer, :dwUpper, :pointer)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  # NOTE: Experimental for now, may remove this marshalling stuff later
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def marshal_dump
         | 
| 40 | 
            +
                    [self[:dwLower].read_ulong, self[:dwUpper].read_ulong]
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def marshal_load(values)
         | 
| 44 | 
            +
                    self[:dwLower] = FFI::MemoryPointer.new(:ulong)
         | 
| 45 | 
            +
                    self[:dwUpper] = FFI::MemoryPointer.new(:ulong)
         | 
| 46 | 
            +
                    self[:dwLower].write_ulong(values[0])
         | 
| 47 | 
            +
                    self[:dwUpper].write_ulong(values[1])
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                CredHandle = SecHandle
         | 
| 52 | 
            +
                CtxtHandle = SecHandle
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                class TimeStamp < FFI::Struct
         | 
| 55 | 
            +
                  layout(:dwLowDateTime, :ulong, :dwHighDateTime, :ulong)
         | 
| 56 | 
            +
                  
         | 
| 57 | 
            +
                  def marshal_dump
         | 
| 58 | 
            +
                    [self[:dwLowDateTime], self[:dwHighDateTime]]
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def marshal_load(values)
         | 
| 62 | 
            +
                    self[:dwLowDateTime] = values[0]
         | 
| 63 | 
            +
                    self[:dwHighDateTime] = values[1]
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                class SecBuffer < FFI::Struct
         | 
| 68 | 
            +
                  layout(
         | 
| 69 | 
            +
                    :cbBuffer, :ulong,
         | 
| 70 | 
            +
                    :BufferType, :ulong,
         | 
| 71 | 
            +
                    :pvBuffer, :pointer
         | 
| 72 | 
            +
                  )
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  def init(token = nil)
         | 
| 75 | 
            +
                    self[:BufferType] = 2 # SECBUFFER_TOKEN
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    if token
         | 
| 78 | 
            +
                      self[:cbBuffer] = token.size
         | 
| 79 | 
            +
                      self[:pvBuffer] = FFI::MemoryPointer.from_string(token)
         | 
| 80 | 
            +
                    else
         | 
| 81 | 
            +
                      self[:cbBuffer] = Windows::Constants::TOKENBUFSIZE # Our TOKENBUFSIZE = 4096
         | 
| 82 | 
            +
                      self[:pvBuffer] = FFI::MemoryPointer.new(:char, Windows::Constants::TOKENBUFSIZE)
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    self
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                  
         | 
| 88 | 
            +
                  def to_ruby_s
         | 
| 89 | 
            +
                    bsize = self[:cbBuffer]
         | 
| 90 | 
            +
                    bsize > 0 ? self[:pvBuffer].read_string_length(bsize) : nil
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                class SecBufferDesc < FFI::Struct
         | 
| 95 | 
            +
                  layout(
         | 
| 96 | 
            +
                    :ulVersion, :ulong,
         | 
| 97 | 
            +
                    :cBuffers, :ulong,
         | 
| 98 | 
            +
                    :pBuffers, :pointer
         | 
| 99 | 
            +
                  )
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  def init(sec_buffer)
         | 
| 102 | 
            +
                    self[:ulVersion] = Windows::Constants::SECBUFFER_VERSION
         | 
| 103 | 
            +
                    self[:cBuffers]  = 1
         | 
| 104 | 
            +
                    self[:pBuffers]  = sec_buffer
         | 
| 105 | 
            +
                    self
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
                  
         | 
| 108 | 
            +
                  def marshal_dump
         | 
| 109 | 
            +
                    buffer = SecBuffer.new(self[:pBuffers])
         | 
| 110 | 
            +
                    buffer.to_ruby_s
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
                  
         | 
| 113 | 
            +
                  def marshal_load(content)
         | 
| 114 | 
            +
                    buffer = SecBuffer.new(self[:pBuffers])
         | 
| 115 | 
            +
                    buffer[:cbBuffer] = content.length
         | 
| 116 | 
            +
                    buffer[:pvBuffer].write_string(content)
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                class SecPkgInfo < FFI::Struct
         | 
| 121 | 
            +
                  layout(
         | 
| 122 | 
            +
                    :fCapabilities, :ulong,
         | 
| 123 | 
            +
                    :wVersion, :ushort,
         | 
| 124 | 
            +
                    :wRPCID, :ushort,
         | 
| 125 | 
            +
                    :cbMaxToken, :ulong,
         | 
| 126 | 
            +
                    :Name, :string,
         | 
| 127 | 
            +
                    :Comment, :string
         | 
| 128 | 
            +
                  )
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                class SecPkgContext_Names < FFI::Struct
         | 
| 132 | 
            +
                  layout(:sUserName, :pointer)
         | 
| 133 | 
            +
                  
         | 
| 134 | 
            +
                  def marshal_load(username)
         | 
| 135 | 
            +
                    self[:sUserName] = FFI::MemoryPointer.from_string(username)
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
                  
         | 
| 138 | 
            +
                  def to_ruby_s
         | 
| 139 | 
            +
                    self[:sUserName].null? ? nil : self[:sUserName].read_string
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
                  
         | 
| 142 | 
            +
                  def to_username_ptr
         | 
| 143 | 
            +
                    self[:sUserName]
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
              end
         | 
| 147 | 
            +
            end
         | 
    
        data/test/all_tests.rb
    ADDED
    
    
| @@ -0,0 +1,417 @@ | |
| 1 | 
            +
            ########################################################################
         | 
| 2 | 
            +
            # Tests for the Win32::SSPI::Negotiate::Client class.
         | 
| 3 | 
            +
            ########################################################################
         | 
| 4 | 
            +
            require 'test-unit'
         | 
| 5 | 
            +
            require 'win32/sspi/negotiate/client'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class TC_Win32_SSPI_Negotiate_Client < Test::Unit::TestCase
         | 
| 8 | 
            +
              SPN = "HTTP/virtual-server.gas.local"
         | 
| 9 | 
            +
              MockCredentialHandle = [777,888]
         | 
| 10 | 
            +
              MockTimeStamp = [0x000000FF,0xFF000000]
         | 
| 11 | 
            +
              MockContextHandle = [123,987]
         | 
| 12 | 
            +
              MockSecBufferContent = Random.new.bytes(128)
         | 
| 13 | 
            +
              ContextAttr = Windows::Constants::ISC_REQ_CONFIDENTIALITY | 
         | 
| 14 | 
            +
                            Windows::Constants::ISC_REQ_REPLAY_DETECT | 
         | 
| 15 | 
            +
                            Windows::Constants::ISC_REQ_CONNECTION
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def setup
         | 
| 18 | 
            +
                @client = Win32::SSPI::Negotiate::Client.new(spn:SPN)
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
              
         | 
| 21 | 
            +
              # assert helper that helps reduce test duplication
         | 
| 22 | 
            +
              def assert_client_call_state(client)
         | 
| 23 | 
            +
                acquire_args = client.retrieve_state(:acquire)
         | 
| 24 | 
            +
                refute_nil acquire_args
         | 
| 25 | 
            +
                assert_equal 9, acquire_args.length
         | 
| 26 | 
            +
                
         | 
| 27 | 
            +
                isc_args = client.retrieve_state(:isc)
         | 
| 28 | 
            +
                refute_nil isc_args
         | 
| 29 | 
            +
                assert_equal 12, isc_args.length
         | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                dsc_args = client.retrieve_state(:dsc)
         | 
| 32 | 
            +
                refute_nil dsc_args
         | 
| 33 | 
            +
                assert_equal 1, dsc_args.length
         | 
| 34 | 
            +
                
         | 
| 35 | 
            +
                fch_args = client.retrieve_state(:fch)
         | 
| 36 | 
            +
                refute_nil fch_args
         | 
| 37 | 
            +
                assert_equal 1, fch_args.length
         | 
| 38 | 
            +
                
         | 
| 39 | 
            +
                assert_nil client.instance_variable_get(:@credentials_handle)
         | 
| 40 | 
            +
                assert_nil client.instance_variable_get(:@context_handle)
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
              
         | 
| 43 | 
            +
              def assert_base64_http_header(header,auth_type)
         | 
| 44 | 
            +
                if Win32::SSPI::API::Common::AUTH_TYPE_NEGOTIATE == auth_type
         | 
| 45 | 
            +
                  assert_match( /\ANegotiate \p{Print}+={,2}\z/,header)
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
                if Win32::SSPI::API::Common::AUTH_TYPE_NTLM == auth_type
         | 
| 48 | 
            +
                  assert_match( /\ANTLM \p{Print}+={,2}\z/,header)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def test_spn_basic_functionality
         | 
| 53 | 
            +
                assert_respond_to(@client, :spn)
         | 
| 54 | 
            +
                assert_nothing_raised{ @client.spn }
         | 
| 55 | 
            +
                assert_kind_of(String, @client.spn)
         | 
| 56 | 
            +
                assert_equal "HTTP/virtual-server.gas.local", @client.spn
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def test_auth_type_basic_functionality
         | 
| 60 | 
            +
                assert_respond_to(@client, :auth_type)
         | 
| 61 | 
            +
                assert_nothing_raised{ @client.auth_type }
         | 
| 62 | 
            +
                assert_kind_of(String, @client.auth_type)
         | 
| 63 | 
            +
                assert_equal Win32::SSPI::API::Common::AUTH_TYPE_NEGOTIATE, @client.auth_type
         | 
| 64 | 
            +
                
         | 
| 65 | 
            +
                client = Win32::SSPI::Negotiate::Client.new(spn:SPN, auth_type:"Kerberos")
         | 
| 66 | 
            +
                assert_equal "Kerberos", client.auth_type
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              def test_token_basic_functionality
         | 
| 70 | 
            +
                assert_respond_to(@client, :token)
         | 
| 71 | 
            +
                assert_nothing_raised{ @client.token }
         | 
| 72 | 
            +
                assert_nil @client.token
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              def test_acquire_handle_basic_functionality
         | 
| 76 | 
            +
                assert_respond_to(@client, :acquire_handle)
         | 
| 77 | 
            +
                assert_equal 0, @client.method(:acquire_handle).arity
         | 
| 78 | 
            +
                assert_respond_to(@client, :acquire_credentials_handle)
         | 
| 79 | 
            +
                assert_equal 9, @client.method(:acquire_credentials_handle).arity
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              def test_initialize_context_basic_functionality
         | 
| 83 | 
            +
                assert_respond_to(@client, :initialize_context)
         | 
| 84 | 
            +
                assert_equal( -1, @client.method(:initialize_context).arity)
         | 
| 85 | 
            +
                assert_respond_to(@client, :initialize_security_context)
         | 
| 86 | 
            +
                assert_equal 12, @client.method(:initialize_security_context).arity
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              def test_acquire_handle_invokes_windows_api_as_expected
         | 
| 90 | 
            +
                client = Class.new(MockNegotiateClient).new(spn:SPN)
         | 
| 91 | 
            +
                assert_nothing_raised{ @status = client.acquire_handle }
         | 
| 92 | 
            +
                assert_equal Windows::Constants::SEC_E_OK, @status
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                args = client.retrieve_state(:acquire)
         | 
| 95 | 
            +
                assert_equal 9, args.length, "acquire_credentials_handle should have 9 arguments"
         | 
| 96 | 
            +
                assert_equal SPN, args[0], "unexpected psz_principal"
         | 
| 97 | 
            +
                assert_equal Win32::SSPI::API::Common::AUTH_TYPE_NEGOTIATE, args[1], "unexpected psz_package"
         | 
| 98 | 
            +
                assert_equal Windows::Constants::SECPKG_CRED_OUTBOUND, args[2], "unexpected f_credentialuse"
         | 
| 99 | 
            +
                assert_nil args[3], "unexpected pv_logonid"
         | 
| 100 | 
            +
                assert_nil args[4], "unexpected p_authdata"
         | 
| 101 | 
            +
                assert_nil args[5], "unexpected p_getkeyfn"
         | 
| 102 | 
            +
                assert_nil args[6], "unexpected p_getkeyarg"
         | 
| 103 | 
            +
                assert_kind_of Windows::Structs::CredHandle, args[7], "unexpected ph_newcredentials"
         | 
| 104 | 
            +
                assert_equal MockCredentialHandle, args[7].marshal_dump
         | 
| 105 | 
            +
                assert_kind_of Windows::Structs::TimeStamp, args[8], "unexpected pts_expiry"
         | 
| 106 | 
            +
                assert_equal MockTimeStamp, args[8].marshal_dump
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
              
         | 
| 109 | 
            +
              def test_acquire_handle_memoizes_handle
         | 
| 110 | 
            +
                client = Class.new(MockNegotiateClient).new(spn:SPN)
         | 
| 111 | 
            +
                assert_nothing_raised{ client.acquire_handle }
         | 
| 112 | 
            +
                assert_nothing_raised{ @status = client.acquire_handle }
         | 
| 113 | 
            +
                assert_equal Windows::Constants::SEC_E_OK, @status
         | 
| 114 | 
            +
                assert_equal 9, client.retrieve_state(:acquire).length
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
              
         | 
| 117 | 
            +
              def test_acquire_handle_raises_when_windows_api_returns_failed_status
         | 
| 118 | 
            +
                client = Class.new(MockNegotiateClient) do
         | 
| 119 | 
            +
                  def acquire_credentials_handle(*args)
         | 
| 120 | 
            +
                    capture_state(:acquire, args)
         | 
| 121 | 
            +
                    return Windows::Constants::SEC_E_WRONG_PRINCIPAL
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
                end.new(spn:SPN)
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                exception = assert_raises(SecurityStatusError){ client.acquire_handle }
         | 
| 126 | 
            +
                assert_not_nil exception
         | 
| 127 | 
            +
                expected_message = <<-EOM
         | 
| 128 | 
            +
            AcquireCredentialsHandle:
         | 
| 129 | 
            +
            ffi_errno:0 win32_status:0x80090322
         | 
| 130 | 
            +
            win32 message:The target principal name is incorrect.\r
         | 
| 131 | 
            +
            EOM
         | 
| 132 | 
            +
                assert_equal expected_message, exception.message
         | 
| 133 | 
            +
              end
         | 
| 134 | 
            +
              
         | 
| 135 | 
            +
              def test_initialize_context_invokes_windows_api_as_expected
         | 
| 136 | 
            +
                client = Class.new(MockNegotiateClient).new(spn:SPN)
         | 
| 137 | 
            +
                assert_nothing_raised{ client.acquire_handle }
         | 
| 138 | 
            +
                assert_nothing_raised{ @status = client.initialize_context }
         | 
| 139 | 
            +
                assert_equal Windows::Constants::SEC_I_CONTINUE_NEEDED, @status
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                args = client.retrieve_state(:isc)
         | 
| 142 | 
            +
                assert_equal 12, args.length, "unexpected arguments"
         | 
| 143 | 
            +
                assert_kind_of Windows::Structs::CredHandle, args[0], "unexpected ph_credentials"
         | 
| 144 | 
            +
                assert_equal MockCredentialHandle, args[0].marshal_dump
         | 
| 145 | 
            +
                assert_nil args[1], "unexpected ph_context"
         | 
| 146 | 
            +
                assert_equal SPN, args[2], "unexpected psz_targetname"
         | 
| 147 | 
            +
                
         | 
| 148 | 
            +
                assert_equal ContextAttr, args[3], "unexpected f_contextreq"
         | 
| 149 | 
            +
                assert_equal 0, args[4], "unexpected reserved1"
         | 
| 150 | 
            +
                assert_equal Windows::Constants::SECURITY_NETWORK_DREP, args[5], "unexpected targetrep"
         | 
| 151 | 
            +
                assert_nil args[6], "unexpected p_input"
         | 
| 152 | 
            +
                assert_equal 0, args[7], "unexpected reserved2"
         | 
| 153 | 
            +
                assert_kind_of Windows::Structs::CtxtHandle, args[8], "unexpected ph_newcontext"
         | 
| 154 | 
            +
                assert_equal MockContextHandle, args[8].marshal_dump
         | 
| 155 | 
            +
                assert_kind_of Windows::Structs::SecBufferDesc, args[9], "unexpected p_output"
         | 
| 156 | 
            +
                assert_equal MockSecBufferContent, client.token
         | 
| 157 | 
            +
                assert_kind_of FFI::MemoryPointer, args[10], "unexpected pf_contextattr"
         | 
| 158 | 
            +
                assert_equal ContextAttr, args[10].read_ulong
         | 
| 159 | 
            +
                assert_kind_of Windows::Structs::TimeStamp, args[11], "unexpected pts_expiry"
         | 
| 160 | 
            +
                assert_equal MockTimeStamp, args[11].marshal_dump
         | 
| 161 | 
            +
              end
         | 
| 162 | 
            +
              
         | 
| 163 | 
            +
              def test_initialize_context_raises_when_windows_api_returns_failed_status
         | 
| 164 | 
            +
                client = Class.new(MockNegotiateClient) do
         | 
| 165 | 
            +
                  def initialize_security_context(*args)
         | 
| 166 | 
            +
                    capture_state(:isc, args)
         | 
| 167 | 
            +
                    return Windows::Constants::SEC_E_INVALID_TOKEN
         | 
| 168 | 
            +
                  end
         | 
| 169 | 
            +
                end.new(spn:SPN)
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                assert_nothing_raised{ client.acquire_handle }
         | 
| 172 | 
            +
                exception = assert_raises(SecurityStatusError){ client.initialize_context }
         | 
| 173 | 
            +
                assert_not_nil exception
         | 
| 174 | 
            +
                expected_message = <<-EOM
         | 
| 175 | 
            +
            InitializeSecurityContext:
         | 
| 176 | 
            +
            ffi_errno:0 win32_status:0x80090308
         | 
| 177 | 
            +
            win32 message:The token supplied to the function is invalid\r
         | 
| 178 | 
            +
            EOM
         | 
| 179 | 
            +
                assert_equal expected_message, exception.message
         | 
| 180 | 
            +
              end
         | 
| 181 | 
            +
              
         | 
| 182 | 
            +
              def test_free_context_and_credentials
         | 
| 183 | 
            +
                client = Class.new(MockNegotiateClient).new(spn:SPN)
         | 
| 184 | 
            +
                credentials = client.create_credhandle(MockCredentialHandle)
         | 
| 185 | 
            +
                context = client.create_ctxhandle(MockContextHandle)
         | 
| 186 | 
            +
                result = client.free_context_and_credentials(context,credentials)
         | 
| 187 | 
            +
                status_ok = Windows::Constants::SEC_E_OK
         | 
| 188 | 
            +
                assert_equal({name:"",status:status_ok,dsc_status:status_ok,fch_status:status_ok}, result)
         | 
| 189 | 
            +
              end
         | 
| 190 | 
            +
              
         | 
| 191 | 
            +
              def test_free_context_and_credentials_when_failed_delete
         | 
| 192 | 
            +
                client = Class.new(MockNegotiateClient) do
         | 
| 193 | 
            +
                  def delete_security_context(*args)
         | 
| 194 | 
            +
                    return Windows::Constants::SEC_E_INVALID_HANDLE
         | 
| 195 | 
            +
                  end
         | 
| 196 | 
            +
                end.new(spn:SPN)
         | 
| 197 | 
            +
                credentials = client.create_credhandle(MockCredentialHandle)
         | 
| 198 | 
            +
                context = client.create_ctxhandle(MockContextHandle)
         | 
| 199 | 
            +
                result = client.free_context_and_credentials(context,credentials)
         | 
| 200 | 
            +
                status_ok = Windows::Constants::SEC_E_OK
         | 
| 201 | 
            +
                status_failed = Windows::Constants::SEC_E_INVALID_HANDLE
         | 
| 202 | 
            +
                expected_result = {name:"DeleteSecurityContext",
         | 
| 203 | 
            +
                                    status:status_failed, dsc_status:status_failed, fch_status:status_ok}
         | 
| 204 | 
            +
                assert_equal expected_result, result
         | 
| 205 | 
            +
              end
         | 
| 206 | 
            +
              
         | 
| 207 | 
            +
              def test_free_context_and_credentials_when_failed_free
         | 
| 208 | 
            +
                client = Class.new(MockNegotiateClient) do
         | 
| 209 | 
            +
                  def free_credentials_handle(*args)
         | 
| 210 | 
            +
                    return Windows::Constants::SEC_E_INVALID_HANDLE
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
                end.new(spn:SPN)
         | 
| 213 | 
            +
                credentials = client.create_credhandle(MockCredentialHandle)
         | 
| 214 | 
            +
                context = client.create_ctxhandle(MockContextHandle)
         | 
| 215 | 
            +
                result = client.free_context_and_credentials(context,credentials)
         | 
| 216 | 
            +
                status_ok = Windows::Constants::SEC_E_OK
         | 
| 217 | 
            +
                status_failed = Windows::Constants::SEC_E_INVALID_HANDLE
         | 
| 218 | 
            +
                expected_result = {name:"FreeCredentialsHandle",
         | 
| 219 | 
            +
                                    status:status_failed, dsc_status:status_ok, fch_status:status_failed}
         | 
| 220 | 
            +
                assert_equal expected_result, result
         | 
| 221 | 
            +
              end
         | 
| 222 | 
            +
              
         | 
| 223 | 
            +
              def test_free_context_and_credentials_when_both_fail
         | 
| 224 | 
            +
                client = Class.new(MockNegotiateClient) do
         | 
| 225 | 
            +
                  def delete_security_context(*args)
         | 
| 226 | 
            +
                    return Windows::Constants::SEC_E_INVALID_HANDLE
         | 
| 227 | 
            +
                  end
         | 
| 228 | 
            +
                  def free_credentials_handle(*args)
         | 
| 229 | 
            +
                    return Windows::Constants::SEC_E_INVALID_HANDLE
         | 
| 230 | 
            +
                  end
         | 
| 231 | 
            +
                end.new(spn:SPN)
         | 
| 232 | 
            +
                credentials = client.create_credhandle(MockCredentialHandle)
         | 
| 233 | 
            +
                context = client.create_ctxhandle(MockContextHandle)
         | 
| 234 | 
            +
                result = client.free_context_and_credentials(context,credentials)
         | 
| 235 | 
            +
                status_failed = Windows::Constants::SEC_E_INVALID_HANDLE
         | 
| 236 | 
            +
                expected_result = {name:"FreeCredentialsHandle",
         | 
| 237 | 
            +
                                    status:status_failed, dsc_status:status_failed, fch_status:status_failed}
         | 
| 238 | 
            +
                assert_equal expected_result, result
         | 
| 239 | 
            +
              end
         | 
| 240 | 
            +
              
         | 
| 241 | 
            +
              def test_both_handles_freed_when_free_handles_raises
         | 
| 242 | 
            +
                client = Class.new(MockNegotiateClient) do
         | 
| 243 | 
            +
                  def delete_security_context(*args)
         | 
| 244 | 
            +
                    capture_state(:dsc, args)
         | 
| 245 | 
            +
                    return Windows::Constants::SEC_E_INVALID_TOKEN
         | 
| 246 | 
            +
                  end
         | 
| 247 | 
            +
                end.new(spn:SPN)
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                assert_nothing_raised{ client.acquire_handle }
         | 
| 250 | 
            +
                assert_nothing_raised{ client.initialize_context }
         | 
| 251 | 
            +
                
         | 
| 252 | 
            +
                refute_nil client.instance_variable_get(:@context_handle)
         | 
| 253 | 
            +
                refute_nil client.instance_variable_get(:@credentials_handle)
         | 
| 254 | 
            +
                
         | 
| 255 | 
            +
                assert_raises{ client.free_handles }
         | 
| 256 | 
            +
                
         | 
| 257 | 
            +
                assert_nil client.instance_variable_get(:@context_handle)
         | 
| 258 | 
            +
                assert_nil client.instance_variable_get(:@credentials_handle)
         | 
| 259 | 
            +
              end
         | 
| 260 | 
            +
              
         | 
| 261 | 
            +
              def test_acquire_handle_invokes_windows_api_as_expected_with_ntlm_auth_type
         | 
| 262 | 
            +
                client = Class.new(MockNegotiateClient).new(auth_type:Win32::SSPI::API::Common::AUTH_TYPE_NTLM)
         | 
| 263 | 
            +
                assert_nothing_raised{ @status = client.acquire_handle }
         | 
| 264 | 
            +
                assert_equal Windows::Constants::SEC_E_OK, @status
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                args = client.retrieve_state(:acquire)
         | 
| 267 | 
            +
                assert_equal 9, args.length, "acquire_credentials_handle should have 9 arguments"
         | 
| 268 | 
            +
                assert_nil args[0], "unexpected psz_principal"
         | 
| 269 | 
            +
                assert_equal Win32::SSPI::API::Common::AUTH_TYPE_NTLM, args[1], "unexpected psz_package"
         | 
| 270 | 
            +
                assert_equal Windows::Constants::SECPKG_CRED_OUTBOUND, args[2], "unexpected f_credentialuse"
         | 
| 271 | 
            +
                assert_nil args[3], "unexpected pv_logonid"
         | 
| 272 | 
            +
                assert_kind_of Windows::Structs::SEC_WINNT_AUTH_IDENTITY, args[4], "unexpected p_authdata"
         | 
| 273 | 
            +
                assert_equal ENV['USERNAME'], args[4].user_to_ruby_s
         | 
| 274 | 
            +
                assert_equal ENV['USERDOMAIN'], args[4].domain_to_ruby_s
         | 
| 275 | 
            +
                assert_nil args[5], "unexpected p_getkeyfn"
         | 
| 276 | 
            +
                assert_nil args[6], "unexpected p_getkeyarg"
         | 
| 277 | 
            +
                assert_kind_of Windows::Structs::CredHandle, args[7], "unexpected ph_newcredentials"
         | 
| 278 | 
            +
                assert_equal MockCredentialHandle, args[7].marshal_dump
         | 
| 279 | 
            +
                assert_kind_of Windows::Structs::TimeStamp, args[8], "unexpected pts_expiry"
         | 
| 280 | 
            +
                assert_equal MockTimeStamp, args[8].marshal_dump
         | 
| 281 | 
            +
              end
         | 
| 282 | 
            +
              
         | 
| 283 | 
            +
              def test_acquire_handle_invokes_windows_api_as_expected_with_ntlm_auth_type_and_supplied_credentials
         | 
| 284 | 
            +
                client = Class.new(MockNegotiateClient).new(
         | 
| 285 | 
            +
                    auth_type:Win32::SSPI::API::Common::AUTH_TYPE_NTLM,
         | 
| 286 | 
            +
                    username:'joey',
         | 
| 287 | 
            +
                    domain:'local',
         | 
| 288 | 
            +
                    password:'kskeidksi'
         | 
| 289 | 
            +
                  )
         | 
| 290 | 
            +
                assert_nothing_raised{ @status = client.acquire_handle }
         | 
| 291 | 
            +
                assert_equal Windows::Constants::SEC_E_OK, @status
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                args = client.retrieve_state(:acquire)
         | 
| 294 | 
            +
                assert_equal 9, args.length, "acquire_credentials_handle should have 9 arguments"
         | 
| 295 | 
            +
                assert_nil args[0], "unexpected psz_principal"
         | 
| 296 | 
            +
                assert_equal Win32::SSPI::API::Common::AUTH_TYPE_NTLM, args[1], "unexpected psz_package"
         | 
| 297 | 
            +
                assert_equal Windows::Constants::SECPKG_CRED_OUTBOUND, args[2], "unexpected f_credentialuse"
         | 
| 298 | 
            +
                assert_nil args[3], "unexpected pv_logonid"
         | 
| 299 | 
            +
                assert_kind_of Windows::Structs::SEC_WINNT_AUTH_IDENTITY, args[4], "unexpected p_authdata"
         | 
| 300 | 
            +
                assert_equal 'joey', args[4].user_to_ruby_s
         | 
| 301 | 
            +
                assert_equal 'local', args[4].domain_to_ruby_s
         | 
| 302 | 
            +
                assert_equal 'kskeidksi', args[4].password_to_ruby_s
         | 
| 303 | 
            +
                assert_nil args[5], "unexpected p_getkeyfn"
         | 
| 304 | 
            +
                assert_nil args[6], "unexpected p_getkeyarg"
         | 
| 305 | 
            +
                assert_kind_of Windows::Structs::CredHandle, args[7], "unexpected ph_newcredentials"
         | 
| 306 | 
            +
                assert_equal MockCredentialHandle, args[7].marshal_dump
         | 
| 307 | 
            +
                assert_kind_of Windows::Structs::TimeStamp, args[8], "unexpected pts_expiry"
         | 
| 308 | 
            +
                assert_equal MockTimeStamp, args[8].marshal_dump
         | 
| 309 | 
            +
              end
         | 
| 310 | 
            +
              
         | 
| 311 | 
            +
              def test_http_authenticate
         | 
| 312 | 
            +
                client = Class.new(MockNegotiateClient).new(spn:SPN)
         | 
| 313 | 
            +
                counter = 0
         | 
| 314 | 
            +
                client.http_authenticate do |header|
         | 
| 315 | 
            +
                  counter += 1
         | 
| 316 | 
            +
                  fail "loop failed to complete in a reasonable iteration count" if counter > 3
         | 
| 317 | 
            +
                  assert_base64_http_header(header,Win32::SSPI::API::Common::AUTH_TYPE_NEGOTIATE)
         | 
| 318 | 
            +
                  header
         | 
| 319 | 
            +
                end
         | 
| 320 | 
            +
                
         | 
| 321 | 
            +
                assert_equal 1, counter
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                assert_client_call_state(client)
         | 
| 324 | 
            +
              end
         | 
| 325 | 
            +
              
         | 
| 326 | 
            +
              def test_http_authenticate_with_ntlm_protocol
         | 
| 327 | 
            +
                client = Class.new(MockNegotiateClient).new(auth_type:Win32::SSPI::API::Common::AUTH_TYPE_NTLM)
         | 
| 328 | 
            +
                counter = 0
         | 
| 329 | 
            +
                client.http_authenticate do |header|
         | 
| 330 | 
            +
                  counter += 1
         | 
| 331 | 
            +
                  fail "loop failed to complete in a reasonable iteration count" if counter > 3
         | 
| 332 | 
            +
                  assert_base64_http_header(header,Win32::SSPI::API::Common::AUTH_TYPE_NTLM)
         | 
| 333 | 
            +
                  header
         | 
| 334 | 
            +
                end
         | 
| 335 | 
            +
             | 
| 336 | 
            +
                assert_equal 2, counter
         | 
| 337 | 
            +
             | 
| 338 | 
            +
                assert_client_call_state(client)
         | 
| 339 | 
            +
              end
         | 
| 340 | 
            +
              
         | 
| 341 | 
            +
              def test_authenticate
         | 
| 342 | 
            +
                client = Class.new(MockNegotiateClient).new(spn:SPN)
         | 
| 343 | 
            +
                counter = 0
         | 
| 344 | 
            +
                client.authenticate do |token|
         | 
| 345 | 
            +
                  counter += 1
         | 
| 346 | 
            +
                  fail "loop failed to complete in a reasonable iteration count" if counter > 3
         | 
| 347 | 
            +
                  assert_equal MockSecBufferContent, token
         | 
| 348 | 
            +
                  token
         | 
| 349 | 
            +
                end
         | 
| 350 | 
            +
                
         | 
| 351 | 
            +
                assert_equal 1, counter
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                assert_client_call_state(client)
         | 
| 354 | 
            +
              end
         | 
| 355 | 
            +
              
         | 
| 356 | 
            +
              def teardown
         | 
| 357 | 
            +
                @client = nil
         | 
| 358 | 
            +
              end
         | 
| 359 | 
            +
            end
         | 
| 360 | 
            +
             | 
| 361 | 
            +
            class MockNegotiateClient < Win32::SSPI::Negotiate::Client
         | 
| 362 | 
            +
              def acquire_credentials_handle(*args)
         | 
| 363 | 
            +
                s_args = retrieve_state(:acquire) || Array.new
         | 
| 364 | 
            +
                s_args << args
         | 
| 365 | 
            +
                capture_state(:acquire,s_args.flatten)
         | 
| 366 | 
            +
                # this api should return a credential handle in arg[7] and a timestamp in arg[8]
         | 
| 367 | 
            +
                args[7].marshal_load(TC_Win32_SSPI_Negotiate_Client::MockCredentialHandle)
         | 
| 368 | 
            +
                args[8].marshal_load(TC_Win32_SSPI_Negotiate_Client::MockTimeStamp)
         | 
| 369 | 
            +
                return Windows::Constants::SEC_E_OK
         | 
| 370 | 
            +
              end
         | 
| 371 | 
            +
              
         | 
| 372 | 
            +
              def initialize_security_context(*args)
         | 
| 373 | 
            +
                capture_state(:isc, args)
         | 
| 374 | 
            +
                status = Windows::Constants::SEC_E_OK
         | 
| 375 | 
            +
                status = Windows::Constants::SEC_I_CONTINUE_NEEDED if args[6].nil?
         | 
| 376 | 
            +
                # this api should return a new context, p_output, context attr and timestamp
         | 
| 377 | 
            +
                args[8].marshal_load(TC_Win32_SSPI_Negotiate_Client::MockContextHandle)
         | 
| 378 | 
            +
                args[9].marshal_load(TC_Win32_SSPI_Negotiate_Client::MockSecBufferContent)
         | 
| 379 | 
            +
                args[10].write_ulong(TC_Win32_SSPI_Negotiate_Client::ContextAttr)
         | 
| 380 | 
            +
                args[11].marshal_load(TC_Win32_SSPI_Negotiate_Client::MockTimeStamp)
         | 
| 381 | 
            +
                return status
         | 
| 382 | 
            +
              end
         | 
| 383 | 
            +
              
         | 
| 384 | 
            +
              def delete_security_context(*args)
         | 
| 385 | 
            +
                capture_state(:dsc,args)
         | 
| 386 | 
            +
                return Windows::Constants::SEC_E_OK
         | 
| 387 | 
            +
              end
         | 
| 388 | 
            +
              
         | 
| 389 | 
            +
              def free_credentials_handle(*args)
         | 
| 390 | 
            +
                capture_state(:fch,args)
         | 
| 391 | 
            +
                return Windows::Constants::SEC_E_OK
         | 
| 392 | 
            +
              end
         | 
| 393 | 
            +
              
         | 
| 394 | 
            +
              def capture_state(key,value)
         | 
| 395 | 
            +
                self.class.capture_state(key,value)
         | 
| 396 | 
            +
              end
         | 
| 397 | 
            +
              
         | 
| 398 | 
            +
              def retrieve_state(key)
         | 
| 399 | 
            +
                self.class.retrieve_state(key)
         | 
| 400 | 
            +
              end
         | 
| 401 | 
            +
              
         | 
| 402 | 
            +
              def self.state
         | 
| 403 | 
            +
                @state ||= Hash.new
         | 
| 404 | 
            +
              end
         | 
| 405 | 
            +
              
         | 
| 406 | 
            +
              def self.capture_state(key,value)
         | 
| 407 | 
            +
                state[key] = value
         | 
| 408 | 
            +
              end
         | 
| 409 | 
            +
              
         | 
| 410 | 
            +
              def self.retrieve_state(key)
         | 
| 411 | 
            +
                state[key]
         | 
| 412 | 
            +
              end
         | 
| 413 | 
            +
              
         | 
| 414 | 
            +
              def self.clear_state
         | 
| 415 | 
            +
                state.clear
         | 
| 416 | 
            +
              end
         | 
| 417 | 
            +
            end
         |