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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 94ed63fe1f2bf199120cb6e027ad643db0512179
4
+ data.tar.gz: 64236f4dd2ae87f6fb9f56510cf9eef7a09281a7
5
+ SHA512:
6
+ metadata.gz: f47abf6eeec115b0e30fc2881b1493d106ed406b5795f49971531f1566c20e543317d2698c572d38ad51ef772437958ef596bda8341ceab2e3ba31139485d4c5
7
+ data.tar.gz: f1c4cbbcc6bf624cec854aeecf8294f1baa3b5e82ae66969cf103bfd92404ee0cec86c5c0c26a313cc8a744b069a0cb9e3dd1ff68172e4f3909f05ad2a8fc68a
@@ -0,0 +1,8 @@
1
+ MIT License
2
+ Copyright (c) 2015 Gary Sick
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ This project is a fork of djbergers win32-sspi project which
2
+ provided the beginnings of a an FFI implementation of SSPI
3
+ on Windows. This project adds support for Kerberos through the
4
+ SPNEGO/Negotiate protocol.
5
+
6
+ The examples directory has working examples to illustrate
7
+ usage. The sspi_negotiate_*.rb files are client/server
8
+ examples of using Kerberos through SPNEGO/Negotiate protocol.
9
+ In order to be used successfully across multiple systems
10
+ in your domain you must define a Service Principal Name (SPN)
11
+ associated with the user account under which the server is
12
+ running unless the server is running as a Windows Service
13
+ under the LocalSystem account. To establish a SPN use the
14
+ setspn command as follows from a elevated privilege command
15
+ window:
16
+
17
+ ```
18
+ setspn -S HTTP/fqdn-of-your-host USERDOMAIN\USERNAME
19
+ ```
20
+ The SPN you establish must be passed to the Client constructor
21
+ with the spn option in order to succesfully connect and
22
+ authenticate with the server.
23
+
24
+ The negotiate client/server implementations are also capable
25
+ of supporting the NTLM protocol. To do so specify the auth_type
26
+ option as 'NTLM' on the construction of the client. The user name
27
+ and user domain will be taken from the environment and will use
28
+ the current logged in users security context to authenticate with
29
+ the server. Otherwise passing the options username, domain, password
30
+ to the client constructor will establish a different security
31
+ context for the given username to authenticate against.
@@ -0,0 +1,49 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+ require 'rubygems'
5
+ require 'rubygems/package'
6
+
7
+ CLEAN.include('**/*.gem')
8
+
9
+ namespace :gem do
10
+ desc "Create the win32-sspi gem"
11
+ task :create => [:clean] do
12
+ spec = eval(IO.read('win32-sspi.gemspec'))
13
+ Gem::Package.build(spec)
14
+ end
15
+
16
+ desc "Install the win32-sspi gem"
17
+ task :install => [:create] do
18
+ file = Dir["*.gem"].first
19
+ sh "gem install #{file} -l --no-document"
20
+ end
21
+ end
22
+
23
+ namespace :test do
24
+ Rake::TestTask.new(:struct) do |t|
25
+ t.test_files = FileList['test/test_win32_sspi_structure_creates.rb']
26
+ t.warning = true
27
+ t.verbose = true
28
+ end
29
+
30
+ Rake::TestTask.new(:client) do |t|
31
+ t.test_files = FileList['test/test_win32_sspi_negotiate_client.rb']
32
+ t.warning = true
33
+ t.verbose = true
34
+ end
35
+
36
+ Rake::TestTask.new(:server) do |t|
37
+ t.test_files = FileList['test/test_win32_sspi_negotiate_server.rb']
38
+ t.warning = true
39
+ t.verbose = true
40
+ end
41
+
42
+ Rake::TestTask.new(:all) do |t|
43
+ t.test_files = FileList['test/test_win32*']
44
+ t.warning = true
45
+ t.verbose = true
46
+ end
47
+ end
48
+
49
+ task :default => 'test:all'
@@ -0,0 +1,42 @@
1
+ require 'pp'
2
+ require 'net/http'
3
+ unless ENV['WIN32_SSPI_TEST']
4
+ require 'win32-sspi'
5
+ require 'negotiate/client'
6
+ else
7
+ require 'win32/sspi/negotiate/client'
8
+ puts "!!!! running with test environment !!!"
9
+ end
10
+
11
+ class RubySSPIClient
12
+ def self.run(url,auth_type)
13
+ uri = URI.parse(url)
14
+ client = (Win32::SSPI::API::Common::AUTH_TYPE_NEGOTIATE == auth_type) ?
15
+ Win32::SSPI::Negotiate::Client.new(spn:"HTTP/#{uri.host}") :
16
+ Win32::SSPI::Negotiate::Client.new(auth_type:auth_type)
17
+
18
+ Net::HTTP.start(uri.host, uri.port) do |http|
19
+ resp = nil
20
+ client.http_authenticate do |header|
21
+ req = Net::HTTP::Get.new(uri.path)
22
+ req['Authorization'] = header
23
+ resp = http.request(req)
24
+ resp['www-authenticate']
25
+ end
26
+
27
+ puts resp.body if resp.body
28
+ end
29
+ end
30
+ end
31
+
32
+ if __FILE__ == $0
33
+ if ARGV.length < 1
34
+ puts "usage: ruby sspi_negotiate_client.rb url [auth_type (Negotiate|NTLM default=Negotiate)]"
35
+ puts "where: url = http://hostname:port/path"
36
+ exit(0)
37
+ end
38
+
39
+ url = ARGV[0]
40
+ auth_type = (2 == ARGV.length) ? ARGV[1] : Win32::SSPI::API::Common::AUTH_TYPE_NEGOTIATE
41
+ RubySSPIClient.run(url,auth_type)
42
+ end
@@ -0,0 +1,99 @@
1
+ # Attempting to setup an example authenticating server
2
+ require 'webrick'
3
+ unless ENV['WIN32_SSPI_TEST']
4
+ require 'win32-sspi'
5
+ require 'negotiate/server'
6
+ else
7
+ require 'win32/sspi/negotiate/server'
8
+ puts "!!!! running with test environment !!!"
9
+ end
10
+
11
+ # A way to store state across multiple requests
12
+ class StateStore
13
+ def self.state
14
+ @state ||= Hash.new
15
+ end
16
+
17
+ def self.store_state(key,value)
18
+ state[key] = value
19
+ end
20
+
21
+ def self.retrieve_state(key)
22
+ state[key]
23
+ end
24
+
25
+ def self.clear_state
26
+ state.clear
27
+ end
28
+
29
+ def self.retrieve_server(auth_type=Win32::SSPI::API::Common::AUTH_TYPE_NEGOTIATE)
30
+ state[:server] ||= Win32::SSPI::Negotiate::Server.new(auth_type: auth_type)
31
+ state[:server]
32
+ end
33
+ end
34
+
35
+
36
+ class RubySSPIServlet < WEBrick::HTTPServlet::AbstractServlet
37
+ def initialize(server,auth_type)
38
+ super server
39
+ @auth_type = auth_type
40
+ end
41
+
42
+ def do_GET(req,resp)
43
+ if req['Authorization'].nil? || req['Authorization'].empty?
44
+ resp['www-authenticate'] = @auth_type
45
+ resp.status = 401
46
+ return
47
+ end
48
+
49
+ authenticated = false
50
+
51
+ begin
52
+ sspi_server = StateStore.retrieve_server(@auth_type)
53
+ authenticated = sspi_server.http_authenticate(req['Authorization']) do |header,complete|
54
+ resp['www-authenticate'] = header if header
55
+ if complete
56
+ resp.status = 200
57
+ resp['Remote-User'] = sspi_server.username
58
+ resp['Remote-User-Domain'] = sspi_server.domain
59
+ resp['Content-Type'] = "text/plain"
60
+ resp.body = "#{Time.now}: Hello #{sspi_server.username} at #{sspi_server.domain}"
61
+ else
62
+ resp.status = 401
63
+ end
64
+ end
65
+ rescue SecurityStatusError => e
66
+ sspi_server.free_handles
67
+ StateStore.clear_state
68
+
69
+ resp.status = 401
70
+ resp['www-authenticate'] = @auth_type
71
+ resp['Content-Type'] = "text/plain"
72
+ resp.body = e.message
73
+ puts "*** server encountered the following error ***\n #{e.message}"
74
+ return
75
+ end
76
+
77
+ StateStore.clear_state if authenticated
78
+ end
79
+
80
+ def self.run(url,auth_type)
81
+ uri = URI.parse(url)
82
+ s = WEBrick::HTTPServer.new( :Binding=>uri.host, :Port=>uri.port)
83
+ s.mount(uri.path, RubySSPIServlet, auth_type)
84
+ trap("INT") { s.shutdown }
85
+ s.start
86
+ end
87
+ end
88
+
89
+ if $0 == __FILE__
90
+ if ARGV.length < 1
91
+ puts "usage: ruby sspi_negotiate_server.rb url [auth_type (Negotiate|NTLM default=Negotiate)]"
92
+ puts "where: url = http://hostname:port/path"
93
+ exit(0)
94
+ end
95
+
96
+ url = ARGV[0]
97
+ auth_type = (2 == ARGV.length) ? ARGV[1] : Win32::SSPI::API::Common::AUTH_TYPE_NEGOTIATE
98
+ RubySSPIServlet.run(url,auth_type)
99
+ end
@@ -0,0 +1,2 @@
1
+ module Win32
2
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'common'
2
+
3
+ module Win32
4
+ module SSPI
5
+ module API
6
+ module Client
7
+ include Common
8
+
9
+ def initialize_security_context(ph_credential,ph_context,psz_targetname,f_contextreq,reserved1,targetdatarep,p_input,reserved2,ph_newcontext,p_output,pf_contextattr,pts_expiry)
10
+ status = InitializeSecurityContext(ph_credential,ph_context,psz_targetname,f_contextreq,reserved1,targetdatarep,p_input,reserved2,ph_newcontext,p_output,pf_contextattr,pts_expiry)
11
+ return status
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,133 @@
1
+ require 'base64'
2
+ require_relative '../windows/constants'
3
+ require_relative '../windows/structs'
4
+ require_relative '../windows/functions'
5
+
6
+ module Win32
7
+ module SSPI
8
+ module API
9
+ module Common
10
+ include Windows::Constants
11
+ include Windows::Structs
12
+ include Windows::Functions
13
+
14
+ AUTH_TYPE_NEGOTIATE = 'Negotiate'
15
+ AUTH_TYPE_NTLM = 'NTLM'
16
+
17
+ def create_sec_winnt_auth_identity(username,domain,password)
18
+ auth_struct = SEC_WINNT_AUTH_IDENTITY.new
19
+ auth_struct[:Flags] = SEC_WINNT_AUTH_IDENTITY_ANSI
20
+
21
+ if username
22
+ auth_struct[:User] = FFI::MemoryPointer.from_string(username.dup)
23
+ auth_struct[:UserLength] = username.size
24
+ end
25
+
26
+ if domain
27
+ auth_struct[:Domain] = FFI::MemoryPointer.from_string(domain.dup)
28
+ auth_struct[:DomainLength] = domain.size
29
+ end
30
+
31
+ if password
32
+ auth_struct[:Password] = FFI::MemoryPointer.from_string(password.dup)
33
+ auth_struct[:PasswordLength] = password.size
34
+ end
35
+
36
+ auth_struct
37
+ end
38
+
39
+ def create_credhandle(lower=nil,upper=nil)
40
+ result = CredHandle.new
41
+
42
+ if lower && upper
43
+ result.marshal_load([lower,upper])
44
+ end
45
+
46
+ result
47
+ end
48
+
49
+ def create_ctxhandle(lower=nil,upper=nil)
50
+ result = CtxtHandle.new
51
+
52
+ if lower && upper
53
+ result.marshal_load([lower,upper])
54
+ end
55
+
56
+ result
57
+ end
58
+
59
+ def create_timestamp(low=nil,high=nil)
60
+ ts = TimeStamp.new
61
+ if low && high
62
+ ts[:dwLowDateTime] = low
63
+ ts[:dwHighDateTime] = high
64
+ end
65
+ ts
66
+ end
67
+
68
+ def create_secbuffer(content=nil)
69
+ SecBuffer.new.init(content)
70
+ end
71
+
72
+ def create_secbufferdesc(sec_buffer=nil)
73
+ SecBufferDesc.new.init(sec_buffer)
74
+ end
75
+
76
+ def create_secpkg_context_names(name=nil)
77
+ result = SecPkgContext_Names.new
78
+ if name
79
+ result[:sUserName] = FFI::MemoryPointer.from_string(name.dup)
80
+ end
81
+ result
82
+ end
83
+
84
+ def construct_http_header(auth_type, token)
85
+ b64_token = token.nil? ? nil : Base64.strict_encode64(token)
86
+ b64_token.nil? ? "#{auth_type}" : "#{auth_type} #{b64_token}"
87
+ end
88
+
89
+ def de_construct_http_header(header)
90
+ auth_type, b64_token = header.split(' ')
91
+ token = b64_token.nil? ? nil : Base64.strict_decode64(b64_token)
92
+ [auth_type, token]
93
+ end
94
+
95
+ def acquire_credentials_handle(psz_principal,psz_package,f_credentialuse,pv_logonid,p_authdata,p_getkeyfn,pv_getkeyarg,ph_credential,pts_expiry)
96
+ status = AcquireCredentialsHandle(psz_principal,psz_package,f_credentialuse,pv_logonid,p_authdata,p_getkeyfn,pv_getkeyarg,ph_credential,pts_expiry)
97
+ return status
98
+ end
99
+
100
+ def query_context_attributes(ph_context,ul_attribute,p_buffer)
101
+ status = QueryContextAttributes(ph_context, ul_attribute, p_buffer)
102
+ return status
103
+ end
104
+
105
+ def delete_security_context(ph_context)
106
+ status = DeleteSecurityContext(ph_context)
107
+ return status
108
+ end
109
+
110
+ def free_credentials_handle(ph_credential)
111
+ status = FreeCredentialsHandle(ph_credential)
112
+ return status
113
+ end
114
+
115
+ def free_context_and_credentials(context,credentials)
116
+ result = {name:'', status:SEC_E_OK, dsc_status:SEC_E_OK, fch_status:SEC_E_OK}
117
+ status = delete_security_context(context)
118
+ if SEC_E_OK != status
119
+ result[:name], result[:status], result[:dsc_status] = ["DeleteSecurityContext", status, status]
120
+ end
121
+
122
+ status = free_credentials_handle(credentials)
123
+ if SEC_E_OK != status
124
+ result[:name], result[:status], result[:fch_status] = ["FreeCredentialsHandle", status, status]
125
+ end
126
+
127
+ return result
128
+ end
129
+
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'common'
2
+
3
+ module Win32
4
+ module SSPI
5
+ module API
6
+ module Server
7
+ include Common
8
+
9
+ def accept_security_context(ph_credential,ph_context,p_input,f_contextreq,targetdatarep,ph_newcontext,p_output,pf_contextattr,pts_timestamp)
10
+ status = AcceptSecurityContext(ph_credential,ph_context,p_input,f_contextreq,targetdatarep,ph_newcontext,p_output,pf_contextattr,pts_timestamp)
11
+ return status
12
+ end
13
+
14
+ def complete_auth_token(ph_context,p_token)
15
+ status = CompleteAuthToken(ph_context,p_token)
16
+ return status
17
+ end
18
+
19
+ def enumerate_security_packages(pc_packages,pp_packageinfo)
20
+ status = EnumerateSecurityPackages(pc_packages,pp_packageinfo)
21
+ return status
22
+ end
23
+
24
+ def free_context_buffer(pv_contextbuffer)
25
+ status = FreeContextBuffer(pv_contextbuffer)
26
+ return status
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,159 @@
1
+ require_relative '../windows/constants'
2
+ require_relative '../windows/misc'
3
+ require_relative '../api/client'
4
+
5
+ module Win32
6
+ module SSPI
7
+ module Negotiate
8
+ class Client
9
+ include Windows::Constants
10
+ include API::Client
11
+
12
+ attr_reader :spn
13
+ attr_reader :auth_type
14
+ attr_reader :token
15
+
16
+ def initialize(options={})
17
+ @spn = options[:spn]
18
+ @auth_type = options[:auth_type] || AUTH_TYPE_NEGOTIATE
19
+ @token = nil
20
+ @credentials_handle = nil
21
+ @context_handle = nil
22
+ @username = options[:username]
23
+ @domain = options[:domain]
24
+ @password = options[:password]
25
+ end
26
+
27
+ def http_authenticate(&block)
28
+ perform_authenticate(block)
29
+ end
30
+
31
+ def authenticate(&block)
32
+ perform_authenticate(block,false)
33
+ end
34
+
35
+ def perform_authenticate(block,with_http_header=true)
36
+ status = acquire_handle
37
+ if SEC_E_OK == status
38
+ begin
39
+ status = initialize_context(self.token)
40
+ if SEC_I_CONTINUE_NEEDED == status
41
+ token_from_block_result(block.call(block_arg_from_token(with_http_header)),with_http_header)
42
+ end
43
+ end while( SEC_I_CONTINUE_NEEDED == status )
44
+
45
+ # if using NTLM protocol we need to complete the final leg of the authentication
46
+ if AUTH_TYPE_NTLM == self.auth_type && SEC_E_OK == status
47
+ block.call(block_arg_from_token(with_http_header))
48
+ end
49
+
50
+ if SEC_E_OK == status
51
+ free_handles
52
+ end
53
+ end
54
+ end
55
+
56
+ def block_arg_from_token(with_http_header)
57
+ with_http_header ? construct_http_header(self.auth_type,self.token) : self.token
58
+ end
59
+
60
+ def token_from_block_result(block_result,with_http_header)
61
+ if block_result
62
+ if with_http_header
63
+ @auth_type, @token = de_construct_http_header(block_result)
64
+ else
65
+ @token = block_result
66
+ end
67
+ end
68
+ end
69
+
70
+ def acquire_handle
71
+ return SEC_E_OK if @credentials_handle
72
+
73
+ auth_data = nil
74
+ if AUTH_TYPE_NTLM == @auth_type
75
+ @username ||= ENV['USERNAME']
76
+ @domain ||= ENV['USERDOMAIN']
77
+ auth_data = create_sec_winnt_auth_identity(@username,@domain,@password)
78
+ end
79
+
80
+ @credentials_handle = create_credhandle
81
+ expiry = create_timestamp
82
+
83
+ status = acquire_credentials_handle(
84
+ @spn,
85
+ @auth_type,
86
+ SECPKG_CRED_OUTBOUND,
87
+ nil,
88
+ auth_data,
89
+ nil,
90
+ nil,
91
+ @credentials_handle,
92
+ expiry
93
+ )
94
+
95
+ if SEC_E_OK != status
96
+ @credentials_handle = nil
97
+ raise SecurityStatusError.new('AcquireCredentialsHandle', status, FFI.errno)
98
+ end
99
+
100
+ status
101
+ end
102
+
103
+ def initialize_context(token=nil)
104
+ return SEC_E_OK if token.nil? && @context_handle
105
+
106
+ ctx = @context_handle
107
+ @context_handle ||= create_ctxhandle
108
+ context_attributes = FFI::MemoryPointer.new(:ulong)
109
+
110
+ rflags = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION
111
+ expiry = create_timestamp
112
+
113
+ if token
114
+ input_buffer = create_secbuffer(token)
115
+ input_buffer_desc = create_secbufferdesc(input_buffer)
116
+ end
117
+
118
+ output_buffer = create_secbuffer
119
+ output_buffer_desc = create_secbufferdesc(output_buffer)
120
+
121
+ status = initialize_security_context(
122
+ @credentials_handle,
123
+ ctx,
124
+ @spn,
125
+ rflags,
126
+ 0,
127
+ SECURITY_NETWORK_DREP,
128
+ (token ? input_buffer_desc : nil),
129
+ 0,
130
+ @context_handle,
131
+ output_buffer_desc,
132
+ context_attributes,
133
+ expiry
134
+ )
135
+
136
+ a_success = [SEC_E_OK, SEC_I_CONTINUE_NEEDED]
137
+ if a_success.include?(status)
138
+ @token = output_buffer.to_ruby_s
139
+ else
140
+ raise SecurityStatusError.new('InitializeSecurityContext', status, FFI.errno)
141
+ end
142
+
143
+ status
144
+ end
145
+
146
+ def free_handles
147
+ result = free_context_and_credentials(@context_handle, @credentials_handle)
148
+ @context_handle, @credentials_handle = [nil,nil]
149
+
150
+ if SEC_E_OK != result[:status]
151
+ raise SecurityStatusError.new(result[:name], result[:status], FFI.errno)
152
+ end
153
+
154
+ result[:status]
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end