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.
@@ -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
@@ -0,0 +1,2 @@
1
+ require_relative "test_win32_sspi_negotiate_client"
2
+ require_relative "test_win32_sspi_negotiate_server"
@@ -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