Client-server authentication - using SSPI?

浪尽此生 提交于 2019-11-28 05:21:14

Update:

SSPI is the right approach for this. The API isn't too hard to use, but does require a decent-sized project to wrap into C#.

In the process of researching the necessary bits to solve this question, I wrote a project to provide SSPI in .Net. Below I describe the basics of interfacing with the Windows SSPI API so that anybody may replicate my results. If you find yourself wanting to use SSPI in .Net, I may suggest you use the project I created to solve this:

NSspi - A .Net interface to the SSPI API

SSPI provides you raw byte arrays containing authentication tokens that you then decide how to transmit - be it over a socket with binary-formatted messages, a custom XML channel, .Net Remoting, some form of WCF, heck, even a serial port. You get to decide how to deal with them. With SSPI a server can authenticate clients, securely identify the client, and even perform basic message handling procedures like encryption/signing using the security context established with the client.

The SSPI API is documented here: SSPI API overview

Specifically take a look at the following functions:

The typical workflow is that each side will initialize their credentials using AcquireCredentialsHandle. The authentication cycle then starts and progresses as follows:

  • The client invokes InitializeSecurityContext, providing no input tokens, which returns output tokens in the form of a byte array. ISC returns 'ContinueNeeded' to indicate that the authentication cycle is not complete.
  • The client sends the tokens to the server by whichever means it desires.
  • The server feeds the received tokens as input to AcceptSecurityContext and produces its own output tokens. ASC also returns 'ContinueNeeded' to indicate that the authentication cycle is not complete.
  • The server then sends its output tokens to the client.
  • The client provides the servers tokens as input to InitializeSecurityContext, which returns new output tokens.
  • The client sends his new output tokens to the server.
  • ...

This cycle continues until the client sees InitializeSecurityContext return 'OK' and the server sees AcceptSecurityContext return 'OK'. Each function may return 'OK' and still provide an output token (as indicated by a non-null return), to indicate that it still has to send data to the other side. This is how the client knows that its half is done but the server's is still incomplete; and vice versa if the server completes before the client. Which side completes first (returns 'OK') depends on the specific security package being used under the hood by SSPI, and any SSPI consumer should be aware of this.

The information above should be enough for anybody to being interfacing with the SSPI system in order to provide 'Windows Integrated Authentication' in their application and replicate my results.

Below is my earlier answer as I learned how to invoke the SSPI API.


I had forgotten about this question, and coincidentally returned to this problem a few days ago on a whim. I do need to solve this problem in a year or two though :)

It is possible in .Net, and I am currently developing a .Net SSPI wrapper that I intend to publish.

I'm basing my work off of some SSPI samples from Microsoft I found.

The sample contains a C++/CLI managed assembly that implements the necessary parts of the SSPI API (in the folder Microsoft\Samples\Security\SSPI\SSPI extracted from the REMSSPI.exe file). They then have two UIs, a client application and a server application, both written in C# that make use of this API to perform SSPI authentication.

The UIs make use of the .Net remoting facility to tie it all together, but if I understand the SSPI API correctly, the only information that the client and server need to exchange consists of byte[]s containing security context token data, which can easily be integrated into whatever communications infrastructure you want; in my case, a binary protocol of my own design.

Some notes on getting the sample to work - they have the 'SSPI' library source, which best compiles under VS 2005, though I've gotten it to work under 2008; 2010 or above would require some rework since they use language constructs that were deprecated. You may also need to modify header files that are part of your platform SDK, because they make use of const pointer assignments to unconst variables, and I don't know a better way to make the compiler happy (I've never used C++/CLI before).

They do include a compiled SSPI dll in the Microsoft\Samples\Security\SSPI\bin folder. To get the client/server binaries to work, you have to copy that dll to their bin directory, else the fail assembly resolution.

So to summarize:

  • Go here to download the REMSSPI.exe sample self-extracting zip.
  • Extract the REMSSPI.exe file (twice..)
  • Microsoft\Samples\Security\SSPI\
    • bin\ - contains compiled dll Microsoft.Samples.Security.SSPI.dll
    • SSPI\ - contains source to dll
    • Sample\ - contains UI source code
      • bin\ - Contains build UI samples. Copy the SSPI.dll file here and run ControlPanel.Client.exe and ControlPanel.Server.exe

Nikola is correct; there is not a .NET-native way to accomplish what you are doing (at least, not using the low-level .NET Sockets support). You can certainly dive under the covers to do some interop black magic, but if both the client and server are under your control, you may want to consider moving up the stack a little and using a higher-level API such as WCF, which DOES have .NET-native support for Windows Integrated authentication.

Based upon your question and your described environment, you'd be able to use the NetTcpBinding, which offers high performance as well as the plumbing for the authentication/identity flow you're looking for (it also offers a fairly clean way to handle authorization using the ServiceAuthorizationManager class). Without knowing the specifics of your app/service I can't possibly provide a "How To" to implement what you're looking to do, but I can point you at the docs which have a reasonably simple example.

I went totally wrong with WindowsIdentity (which is good for authorization) because I forgot that WCF handles a lot of things with configuration files, endpoint security and message/transport security.

Have you tried with NegotiateStream ? The given example seems to better fit your needs : it uses Kerberos for authenticating before allowing any read/write. Using the CredentialCache.DefaultNetworkCredentials should avoid you to query for a password.

winnie_quest

You may ask: "How does the server confirm a client are who they say they are?"

Answer: If all the roundtrips of handshake could be finished successfully, i.e. both

  • InitializeSecurityContext returns "OK"
  • AcceptSecurityContext returns "OK"

it means the client's Windows login credentials have been confirmed to be real.

AcceptSecurityContext outputs a CtxtHandle security context (through the 6th parameter).

This context handle includes the client's Windows login username. The server can get the client's windows username by calling QueryContextAttributesEx:

SecPkgContext_NativeNames pinfo;
QueryContextAttributesEx(&m_securitycontext, SECPKG_ATTR_NATIVE_NAMES, &pinfo);

This populates a Native Names structure:

SecPkgContext_NativeNames 
{
   SEC_CHAR *sClientName;
   SEC_CHAR *sServerName;
}

the value pinfo.sClientName is the client's real login user name.

Note: The previous handshakes already guarantee the truth of security context, so the server would believe pinfo.sClientName is just the client's real windows username.

Ever tried to work with WindowsIdentity ?
Pure C#/.Net, serializable and GetCurrent() returns the executing account.


Security Token
The user delivers a set of claims to your application piggybacked along with her request. In a Web service, these claims are carried in the security header of the SOAP envelope. In a browser-based Web application, the claims arrive via an HTTP POST from the user’s browser, and may later be cached in a cookie if a session is desired. Regardless of how they arrive, they must be serialized somehow, and this is where security tokens come in. A security token is a serialized set of claims that is digitally signed by the issuing authority. The signature is important – it gives you assurance that the user didn’t just make up a bunch of claims and send them to you. In low security situations where cryptography isn’t necessary or desired, you can use unsigned tokens, but that’s not a scenario I’m going to focus on in this paper. One of the core features in WIF is the ability to create and read security tokens. WIF and the underlying plumbing in the .NET Framework handles all the cryptographic heavy lifting, and presents your application with a set of claims that you can read.

quoted from Windows Identity Foudation WhitePaper

Your primary question was :

"Is there a pure C#/.NET way to authenticate users using their logon credentials ?"

The WindowsIdentity "is" the authentication token issued by your domain controller and seems to me like the best approach at this time.


I knew very few about WindowsIdentity when I first posted but I also felt it would help with your problem and constraints. I've done a lot of reading and finally came to this page.
The introduction of WIF is quite self-explanatory, WindowsIdentity is a new set of the .NET framework designed for Windows-based/Role-based security concerns.
SSPI, Kerberos are parts of the whole Windows authentication process, the logon token obtained by the user/machine/process is granted by the domain controller and cannot be obtained by "simply" instanciating a new WindowsIdentity object. If this kind of illegal instanciation existed, the whole Windows security model (domains, UAC, etc.) would be dead.

Here is a (very!) little console program that throws an exception if you aren't part of the "BUILTIN\Administrateurs" (change the group name according to your own needs). Whenever "Run as Admin", the program finishes without errors.
There is a very large set of permissions and every demand is claim-based (is the identity memeber of xxx ?)

using System;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;

namespace WindowsIdentityTest
{
    class Program
    {
        [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
        static string SomeServerAction()
        { return "Authenticated users can access"; }

        [PrincipalPermission(SecurityAction.Demand, Role = "BUILTIN\\Administrateurs")]
        static string SomeCriticalServerAction()
        { return "Only Admins can access"; }

        static void Main(string[] args)
        {
            //This allows to perform security checks against the current Identity.   
            AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

            try
            {
                Console.WriteLine(SomeServerAction());
                Console.WriteLine(SomeCriticalServerAction());

            }
            catch (SecurityException sec)
            {
                Console.WriteLine(string.Format("{0} : {1}\n------------\n{2}"
                    , sec.GetType()
                    , sec.Message
                    , sec.StackTrace));
            }
            catch (Exception ex)
            {
                Console.WriteLine("This shall not appen.");
            }
            Console.WriteLine("Press enter to quit.");
            Console.ReadLine();
        }
    }
}

I hope this will help.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!