Handles “Negotiate” type authentication. Geared towards authenticating with a proxy server over HTTP
Default request flags for SSPI
functions
NTLM tokens start with this header always. Encoding
alone adds “==” and newline, so remove those
# File tmp/rubies/ruby-3.2.0/ext/win32/lib/win32/sspi.rb, line 245
def initialize(user = nil, domain = nil)
if user.nil? && domain.nil? && ENV["USERNAME"].nil? && ENV["USERDOMAIN"].nil?
raise "A username or domain must be supplied since they cannot be retrieved from the environment"
end
@user = user || ENV["USERNAME"]
@domain = domain || ENV["USERDOMAIN"]
end
Creates a new instance ready for authentication as the given user in the given domain. Defaults to current user and domain as defined by ENV and ENV if no arguments are supplied.
# File tmp/rubies/ruby-3.2.0/ext/win32/lib/win32/sspi.rb, line 230
def NegotiateAuth.proxy_auth_get(http, path, user = nil, domain = nil)
raise "http must respond to :get" unless http.respond_to?(:get)
nego_auth = self.new user, domain
resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
if resp["Proxy-Authenticate"]
resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(resp["Proxy-Authenticate"].split(" ").last.strip) }
end
resp
end
Given a connection and a request path, performs authentication as the current user and returns the response from a GET request. The connection should be a Net::HTTP
object, and it should have been constructed using the Net::HTTP.Proxy method, but anything that responds to “get” will work. If a user and domain are given, will authenticate as the given user. Returns the response received from the get method (usually Net::HTTPResponse
)
# File tmp/rubies/ruby-3.2.0/ext/win32/lib/win32/sspi.rb, line 312
def clean_up
# free structures allocated
@cleaned_up = true
API::FreeCredentialsHandle.call(@credentials.to_p)
API::DeleteSecurityContext.call(@context.to_p)
@context = nil
@credentials = nil
@contextAttributes = nil
end
# File tmp/rubies/ruby-3.2.0/ext/win32/lib/win32/sspi.rb, line 278
def complete_authentication(token)
raise "This object is no longer usable because its resources have been freed." if @cleaned_up
# Nil token OK, just set it to empty string
token = "" if token.nil?
if token.include? "Negotiate"
# If the Negotiate prefix is passed in, assume we are seeing "Negotiate <token>" and get the token.
token = token.split(" ").last
end
if token.include? B64_TOKEN_PREFIX
# indicates base64 encoded token
token = token.strip.unpack("m")[0]
end
outputBuffer = SecurityBuffer.new
result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, @context.to_p, nil,
REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(token).to_p, 0,
@context.to_p,
outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
if result.ok? then
return encode_token(outputBuffer.token)
else
raise "Error: #{result.to_s}"
end
ensure
# need to make sure we don't clean up if we've already cleaned up.
clean_up unless @cleaned_up
end
Takes a token and gets the next token in the Negotiate authentication chain. Token can be Base64
encoded or not. The token can include the “Negotiate” header and it will be stripped. Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned. Token returned is Base64
encoded w/ all new lines removed.
# File tmp/rubies/ruby-3.2.0/ext/win32/lib/win32/sspi.rb, line 332
def encode_token(t)
# encode64 will add newlines every 60 characters so we need to remove those.
[t].pack("m").delete("\n")
end
# File tmp/rubies/ruby-3.2.0/ext/win32/lib/win32/sspi.rb, line 323
def get_credentials
@credentials = CredHandle.new
ts = TimeStamp.new
@identity = Identity.new @user, @domain
result = SSPIResult.new(API::AcquireCredentialsHandleA.call(nil, "Negotiate", SECPKG_CRED_OUTBOUND, nil, @identity.to_p,
nil, nil, @credentials.to_p, ts.to_p))
raise "Error acquire credentials: #{result}" unless result.ok?
end
Gets credentials based on user, domain or both. If both are nil, an error occurs
# File tmp/rubies/ruby-3.2.0/ext/win32/lib/win32/sspi.rb, line 256
def get_initial_token
raise "This object is no longer usable because its resources have been freed." if @cleaned_up
get_credentials
outputBuffer = SecurityBuffer.new
@context = CtxtHandle.new
@contextAttributes = "\0" * 4
result = SSPIResult.new(API::InitializeSecurityContextA.call(@credentials.to_p, nil, nil,
REQUEST_FLAGS,0, SECURITY_NETWORK_DREP, nil, 0, @context.to_p, outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
if result.ok? then
return encode_token(outputBuffer.token)
else
raise "Error: #{result.to_s}"
end
end
Gets the initial Negotiate token. Returns it as a base64 encoded string suitable for use in HTTP. Can be easily decoded, however.