win32-sspi 0.0.1.rc1-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -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