Export (0) Print
Expand All
21 out of 32 rated this helpful - Rate this topic

.NET Remoting Authentication and Authorization Sample - Part I

 

January 2004

Michel Barnett
Microsoft Corporation

Applies to:
   Microsoft® .NET Remoting
   Microsoft Windows®
   Windows Security Support Provider Interface

Summary: Learn about Microsoft.Samples.Security.SSPI, a stand-alone sample application for implementing a security solution across a remoting boundary. It provides authentication, signing, and encryption services that can be used across a range of communication protocols including sockets, RPC, and so on. (33 printed pages)

Download RemSSPI.exe.

Contents

Introduction
Authentication
SSPI
Microsoft.Samples.Security.SSPI
Sample Application
Summary
References

Note   This article is the first in a two-part series describing a security solution for .NET remoting. This first part describes Microsoft.Samples.Security.SSPI—an assembly implementing a managed wrapper around the Microsoft® Windows® Security Support Provider Interface. This provides the core functionality needed to authenticate a client and server as well as sign and encrypt messages sent between the two. This provides the key functionality needed to implement a security solution across a remoting boundary.
The second article in the series describes Microsoft.Samples.Runtime.Remoting.Security, an assembly that leverages Microsoft.Samples.Security.SSPI to provide a security solution specifically for .NET remoting.
Explaining the solution in a two-part series was done because the first part, Microsoft.Samples.Security.SSPI can stand on its own. It provides authentication, signing, and encryption services that can be used across a range of communication protocols: including sockets, RPC, and so on. While this is true, keep in mind that the sample described here was primarily designed to be used as part of a .NET remoting security solution.
Note   This is an update to the original article published in the summer of 2002. This update was mostly done to provide a version of the sample that works with the .NET Framework version 1.1. However, additional features have been added. Mutual authentication is now supported for Kerberos and the Identify flag is now supported for NTLM and Kerberos.
Some implementation details have also been changed in the sample. All classes now implement IDisposable where appropriate, allowing resources to be collected more aggressively than before. This is important for the authentication channel sinks which may run for long periods of time under high load.
As far as this article is concerned, the only significant change is to the section on Kerberos. Additional text has been added to explain the User2User sub-protocol which is now standard on the Microsoft Windows XP and Windows Server 2003 Kerberos implementations.

Introduction

Why should you care about remoting security? Consider a simple scenario that is not remoted (Figure 1).

Figure 1. Non-remoted Foo application

This scenario consists of a single .exe running under Alice's security context. Client.exe instantiates a component called Foo which implements a single public method called OpenFile:

public bool OpenFile(string FilePath)
{
    bool success = true;
    try
    {
        FileStream fs = File.Open(FilePath, FileMode.Open);
        fs.Close();
    }
    catch (UnauthorizedAccessException ex)
    {
        success = false;
    }
    return success;
}

Let's assume that the ACL on Secure.txt has a single entry granting Alice full access to the file. In this case, when Alice calls Foo::OpenFile, the result is successful. So far so good.

Now let's change the picture (Figure 2).

Figure 2. Remoted Foo application

As before, Alice instantiates Foo and calls OpenFile. But this time, Foo isn't instantiated in Client.exe but instead in Server.exe (it isn't terribly important exactly how Foo is remoted, but for this discussion assume that communication between the two processes is via a TCP socket). The key difference between the two scenarios is that this time Foo is running in a process under Bob's security context, not Alice's. When Alice calls OpenFile on the remote instance of Foo, the result is false—access is denied.

Why should this simple configuration change make such a dramatic difference to the behavior of the application? In this case, Secure.txt is the secured object that you're trying to access. The security token of the caller is used to gain access to that object. In the revised picture, that token belongs to Bob, not Alice. Since Bob has not been granted access to Secure.txt, the access attempt fails.

Let's play devil's advocate. Why would Alice's credentials ever be used to access Secure.txt in the remoted scenario? After all, access to Secure.txt is done via a simple comparison between the caller's security token and the secured object's ACL. The caller in this case is clearly Bob. Alice's access token is way over in Client.exe. Alice's credentials couldn't possibly be used to access Secure.txt.

But does this logically make sense? After all, Alice is making the call to Foo. To Alice, Foo is simply a utility class providing a service. The difference between the two scenarios is just a configuration change. Why should moving Foo to a different host make any change to the behavior of the application? What would be nice is if the second scenario acted like the first. In this case, meaning that Alice's credentials are used to access Secure.txt.

What this Article is About

What this article explains is how to make the remoted scenario act like the non-remoted version as well as some of the implications of making it work this way.

The key mechanism to making this happen is impersonation—masking the server's security credentials with that of the caller. In the scenario this means that when Alice calls the remoted version of Foo, Server.exe masks Bob's credentials with those belonging to Alice. Then any work that Foo performs is done as Alice. Effectively, this means that a security token in Server.exe that belongs to Alice needs to be created and Foo can use that impersonation token to open Secure.txt.

I said that this article (and the next in the series) describes a security solution for .NET remoting. But what does "secure" mean? More specifically, I'll explain the process of authentication—determining who the caller is. After all, Server.exe shouldn't impersonate any caller, much less Alice, unless it is sure that the caller is who she says she is. This process involves a couple of protocols that perform the task of authentication. I'll explain how these protocols work.

Next, you need some way to take the authentication protocols and make them concrete—so you can use them programmatically. This is where the Security Support Provider Interface comes in. SSPI is an unmanaged interface that allows us to perform various security functions using authentication protocols supported by the operating system. This includes building impersonation tokens as well as signing and encrypting messages. I'll explain the SSPI interface and which portions you make use of here.

Next, I'll explain the managed version of the SSPI interface that comprises the Microsoft.Samples.Security.SSPI assembly. This is the heart of the .NET remoting security solution that I'll explain in the next article.

Finally, the managed SSPI assembly will be put to use in a sample. This sample demonstrates all of the capabilities of the assembly as well the concepts I explain in this article.

That simple task of creating a security token for Alice in Server.exe (and then masking Bob's token), is what this article is about.

All of the details aside, what you want is a managed equivalent of the Security Support Provider Interface—a library that allows us to factor out all of the details of authentication (client and server side). Also, a library that re-thinks the SSPI design and provides an object-oriented interface more suitable to the .NET environment.

Where to Go from Here

This article provides background material on authentication protocols that is useful to know before diving into the details of the Security Support Provider Interface. If you're already familiar with how Kerberos and NTLM work, skip the Authentication section and go right on to SSPI. The end of this article includes plenty of references you can use to learn about these protocols later.

The SSPI section describes the Security Support Provider Interface. The SSPI serves as the basis of the managed provider that will be built later. If you're already familiar with the SSPI, you can skip to the end of this section and review SSPI in Microsoft.Samples.Security.SSPI to learn about which parts of Security Support Provider Interface that will be used.

The rest of the document starting with Microsoft.Samples.Security.SSPI, goes into the details of the managed provider. Any reader should go through this and remaining sections to learn how it was developed.

Authentication

Authentication is about proving that you are who you say you are. In the scenario described in the overview, this is important because the server component, Foo, intends to do work (in this case open a file) on behalf of the caller. If it is going to do that, it should be certain that the caller is who he says he is. It would be awful if some user other than Alice was able to instantiate Foo, trick it into thinking it really was Alice, and get it to perform work on Alice's behalf.

Authenticating a user, as a concept, is rather straightforward. A user provides his credentials to the system, and the system compares those credentials with a database that contains a matching set of credentials. If they match, the user is authentic. One of the most common examples of this is when you logon to your workstation. You provide your credentials (username and password), and the computer compares those with matching credentials in the account database. If a match is found, you get to log in.

How does authentication work in a remoting scenario? Let's go back to the remoting example.

Figure 3. Providing credentials to Server.exe

Somehow Server.exe has to get a hold of Alice's credentials. Without them, it can't impersonate Alice and do work on her behalf—like opening a file. The most straightforward way to do this is to extend the workstation login scenario previously mentioned. If logging into a workstation is simply a matter of providing a username and password, you can do the same when calling a remote method. So when you call Foo from Client.exe, Alice's username and password will be passed along (Figure 3). Foo can then create a logon session for Alice and open the file with her credentials.

The problem with this scenario is that while it achieves the purpose of authenticating the client to the server, it isn't very secure. Anyone sniffing the connection between Client.exe and Server.exe could capture Alice's password and then do anything with it. This includes playing it back to Foo and fooling it into thinking a malicious caller is really Alice.

So the requirement isn't just to get Alice's credentials to the server; it's really to do that securely. That's why you need some special protocols to achieve this. There are two of them built into Windows that you can leverage for this: NTLM and Kerberos.

Authorities   Before we dive into a description of the authentication protocols, it is worth saying a word about authorities. An authority, for the purpose of this discussion, is an entity that maintains a user account database and is capable of validating a given set of credentials as belonging to one of its accounts. In Windows, a security authority may be a single machine that hasn't joined a domain (in which case the account database is that of the local user accounts). It may also be a domain controller (in which case the account database contains all of the accounts in that domain).
The details of how NTLM and Kerberos function vary depending on which machines the client and server runs on relative to the authority. For a single machine/no domain scenario, authentication is performed against a local account database. In a domain environment where the server is on a different machine than the authority, the local machine must pass on credentials it obtains to the off-machine authority to do validation. This is called pass-through authentication. In a scenario where the client belongs to a different authority than the server, the server must pass on credentials to a different domain altogether. This is called cross-domain authentication.
As I explain each protocol, I'm assuming that the server is running on the same machine as the authority (or domain controller). It is also assumed that the client is on a second, remote machine. In other words, the client and server are separated by a network connection.
In the text below, I'm leaving out any discussion of pass-through and cross-domain authentication. These scenarios are more complex versions of the basic example I describe, but generally they're "more of the same". My intent is to refresh you on the basics of each protocol—not explain every detail. However, I'll leave you with references on these other scenarios if you're looking for more detail.

NTLM

NTLM stands for NT LAN Manager. NTLM is an undocumented but commonly used authentication protocol. It has been built into Windows virtually since it has had networking support, dating back to the LAN Manager days—thus the name. It has always been a part of Microsoft Windows NT® and is also supported in Windows 2000 and Windows XP (mostly for backward compatibility).

NTLM provides a basic mechanism for authenticating a client to a server based on a three-way handshake. The goal is to prove to the server the identity of the client without having to send clear text credentials across a remoting boundary (especially a network).

A typical authentication handshake using NTLM looks something like this (Figure 4).

Figure 4. NTLM authentication

In this case you have two machines: Alice's machine and Bob's machine. Alice is running a process (Client.exe) that initiates an authentication handshake with a process running on Bob's machine (Server.exe). Both machines are part of a domain: Quux.

In the first leg of the handshake a negotiate message is sent from the client to the server (1). This is a request to begin the authentication handshake. At this point the receiver of the negotiate message doesn't know who the request is coming from—only that a response needs to be generated to complete the handshake.

The response is a challenge (2). The challenge is a NONCE—essentially a 64 bit number generated by the server and guaranteed only to be used once (thus the name). Alice will use this to identify herself without sending her clear text credentials.

Alice now needs to send a response to the challenge. To form his response, she takes her password (actually a one-way hash of her password) and uses it as a cryptographic key to encrypt the NONCE. She bundles that along with her principal name and authority and sends that back to the server (3).

When Bob receives the response, he passes it along to his authority (4). The authority now has the response (the encrypted NONCE), a principal name, and authority name (again, assume Alice and Bob are in the same domain (authority)). The authority must use the encrypted NONCE to verify that the given principal (Quux\Alice) is who she says she is. Of course, the authority contains the account database of all users, so it can look up Alice's password, generate a one-way hash and use that to encrypt the original NONCE. If the NONCE it just encrypted matches the response from Alice, the authentication succeeds.

At this point the authority creates a new network logon session for Alice on Bob's machine (5). It also hands back a token for Alice to the server (6).

Life after authentication

The handshake described above proves to the server that Alice is who she says she is. But how does the server know that subsequent requests from Alice really are from her?

The primary purpose of the authentication handshake is of course to prove Alice's identity to the server. But it is also used to generate a session key. The session key is used by both the client and server to prove that subsequent messages are being sent from Alice.

Recall that Alice authenticates herself to the server by generating a response. The response is generated by using Alice's master key (the one-way hash of her password) and using it to encrypt the challenge. So the response = f1(challenge, password hash). The point is that only Alice and her authority know the challenge and her password hash. So when the server receives the response, it knows (through verification from its authority) that the response must be from Alice.

The session key is formed by a different function with the same inputs. The session key = f2(challenge, password hash). Since Alice already knows her password, she discovers this when she receives the challenge from the server. Bob discovers the session key on the server side when the authority authenticates the response and creates a logon session for Alice (remember the authority has the challenge and the password hash and so can form the session key and hand it back to Bob).

The session key can be used to sign or encrypt messages sent between the client and server. Signing a message means that the original message is sent in clear text, accompanied by a Message Authentication Code (MAC). The MAC is essentially a one-way hash of the message where the hash value is encrypted using the session key. When the server receives a message it can use the session key to decrypt the MAC, and compare it with one that it independently calculates. If they match, the message hasn't been tampered with.

The session key can be also be used to encrypt the entire message, which ensures that the message hasn't been tampered with and it is not sent in clear text on the network.

Since the session key is a function of information that only Alice and the authority knows (the challenge and Alice's password hash), a message successfully verified or decrypted on the server side must be from Alice.

Recap

With what you now know you can:

  • Authenticate the client to the server
  • Ensure message integrity
  • Encrypt messages
Why NTLM doesn't support delegation   I haven't talked about delegation, but those already familiar with security programming will recognize the concept. Delegation is where the server is given the authority to act on the client's behalf. When discussed in the context of NTLM or Kerberos, delegation is usually referred to as the ability of a server to act on behalf of the client across more than one network hop.
Those who have worked with NTLM know that the protocol does not support delegation—but why is this so? Consider what would be involved. Let's say that we've already gone through the authentication handshake described above. As a result, Bob's machine has a network logon session for Alice. Let's also say that Server.exe has impersonated Alice. Server.exe can now do work on Alice's behalf (such as open files). Let's say that code in Server.exe now attempts to call into a third machine (Larry's machine) and have a server there do work on Alice's behalf. What happens?
First, Server.exe would send a negotiate message to Larry's Machine. In response, the server on Larry's Machine would generate a challenge and it sends it back to Server.exe. So far, so good.
Now comes the trouble. To generate a response, Server.exe needs the challenge (which it has) and a one way hash of Alice's password. Alice's password was never sent to Server.exe by Alice's Machine (thankfully), so Server.exe can't generate the response. No password hash, no delegation.

Note that as required, clear text credentials are never sent across the wire—yet you can prove to a server who a caller is. There's another protocol that accomplishes this same task called Kerberos.

Kerberos

Kerberos is a well documented and widely used authentication protocol implemented on the Microsoft platform since Windows 2000. Kerberos requires a central authority to run called a Key Distribution Center. Collectively, Kerberos is primarily a set of three protocols that allows a security principal (such as a user or machine) to authenticate itself to another.

Note   The three subprotocols are: the authentication server exchange (AS), the ticket-granting service (TGS) exchange, and the client/server (CS) exchange. I say primarily because there is another subprotocol I'll describe later called User2User.

The scope of operation of a KDC is a Kerberos realm (analogous to a domain). In Windows 2000, a domain controller acts as a Kerberos KDC. Kerberos requires this notion of a central authority, and therefore a Windows domain (NTLM can operate on machines that haven't joined a domain).

The KDC maintains a database of all of the security principals in its realm. As you would expect, each security principal has a master key derived from a password. The KDC also maintains a copy of each principal's master key.

Authentication in Kerberos is based on the notion of tickets.

Kerberos in a nutshell

A Kerberos ticket grants access to a particular secured resource. At a high level, the concept is simple. Say that a client wants to authenticate to a server using Kerberos (Figure 5).

Figure 5. Kerberos before authentication

Since authentication in Kerberos is based on tickets, if Alice wants to authenticate herself to Server.exe, she needs a ticket to Bob (the principal under which Server.exe runs). The place where Alice gets tickets is the KDC, and specifically a part of the KDC called the Ticket-Granting Service (TGS).

The purpose of the TGS is to hand out tickets to secured resources. So, what Alice has to do is ask the TGS for a ticket to Bob. But before Alice can ask the TGS for a ticket she has to prove her identity to the KDC. After all, it wouldn't do for the TGS to hand out tickets to anyone who asked. So, Alice has to talk to a part of the KDC called the Authentication Service (AS).

The AS is responsible for handing out special tickets called ticket-granting tickets. These tickets are so named because they allow their holder to communicate with the TGS and obtain tickets to specific protected resources. Alice can only obtain a ticket-granting ticket by proving her identity to the KDC. Once she has the TGT, she can obtain the specific ticket she needs to access Bob. Once Alice gets that ticket, she can send it along with any message she sends to Bob. When Bob gets that ticket, he knows the caller is authentic, and can begin authorizing Alice for any resources that she's allowed access to.

This is a simplification of what happens, but gives you an idea of how the process works. I'll explain each part in a little more detail. But first I'll say a few words about the heart of the Kerberos protocol: the ticket.

Kerberos tickets

A Kerberos ticket grants access to a secured resource. Tickets are labeled with a principal name (such as Bob) and contain a session key used to securely exchange messages between the client and server.

The contents of a ticket look like this (Figure 6).

Figure 6. Kerberos ticket

A ticket is labeled with the server name & realm it is targeted for. The rest of the ticket is encrypted. I don't list all of the contents of the ticket in the figure but the most important parts are as follows:

  • Server name & realm—Name of principal that the ticket is targeted for (in this case, Quux\Bob).
  • Session key—Used to authenticate data sent between the client and server.
  • Flags—Specifies special ways that the ticket should be used or was obtained.
  • Client name & realm—Name of the principal requesting the ticket (in this case, Quux\Alice). Used to verify the client's identity.
  • End time—The time at which the ticket expires.

The most important part of the ticket is the session key. This allows the client to securely exchange data with the server, which is essentially what the requestor of the ticket is after.

Note also that most of the ticket is encrypted. Only the server name isn't encrypted—so anyone can see for which principal the ticket is intended. The fact that the rest is encrypted ensures that the session key is exchanged securely. How it is encrypted comes into play when studying the Kerberos sub-protocols. There are three primary sub-protocols and each performs a key task in the authentication process (Figure 7).

Figure 7. Kerberos authentication

Next, I'll explain the part each plays in the authentication process.

Authentication Server Exchange (AS)

Before a client can obtain a ticket to a specific resource, he must obtain a special ticket to allow him to do this: the ticket-granting ticket. The purpose of this first subprotocol is to prove the identity of the caller to the KDC and allow callers to obtain a ticket-granting ticket.

The protocol is initiated by the client when he sends a KRB_AS_REQ (authentication service request) to the AS. The AS processes this request and if successful, returns a KRB_AS_REP (authentication service response) to the caller (Figure 8).

Figure 8. Authentication server exchange messages

The KRB_AS_REQ message contains more data than that shown here but I've highlighted some of the more important information for the purposes of this discussion:

  • Pre-authentication data—Used to prove to the AS that the sender of the request is the same principal specified by client name & realm.
  • Options—A request for options on how the ticket should be used (these are confirmed by the flags contained in the ticket in KRB_AS_REP).
  • Client name & realm—The client sending the request (in this case, Quux\Alice)
  • Server Name—In this case you're establishing a session to the Ticket-Granting Service (TGS) Exchange.

The response is a ticket-granting ticket that allows us to communicate with the Ticket-Granting Service. As with all tickets, the contents of the ticket are encrypted using a key—for this subprotocol, the ticket is encrypted using the client's (Quux\Alice) master key. If Alice asks for the ticket-granting ticket then only she can decrypt it and get to its contents (most importantly the session key). This is exactly what you want and it ensures that only Alice (the intended client) can make use of the ticket.

In this case Alice asked for a ticket for use by Alice. But what if Larry asked the AS for a TGT for Alice? The Kerberos protocol actually allows this. This isn't really a security hole since that ticket wouldn't do Larry much good (only Alice can decrypt it and get to the session key). But Kerberos provides for an option to prevent this—and that lies in the first piece of data in the request: the pre-authentication data. The purpose is to put information here that proves who the sender is. In Kerberos parlance the data that you offer as this proof of identity is an Authenticator.

An authenticator is used to prove the identity of the principal sending a message. Why is this important? If a principal, say Bob, receives a ticket targeted for him and it's coming from Alice, how does Bob know the ticket really came from Alice (and isn't being replayed by some malicious caller)? A ticket contains a session key intended for use between the client and server; only those two entities know the session key (of course the KDC knows the session key because it created the ticket in the first place). If Alice can demonstrate that she knows the session key, then Bob can be assured when he receives a message that it really came from Alice. That's what the authenticator is for.

When Alice sends a KRB_AS_REQ message to the AS, she creates an authenticator by encrypting a timestamp with his master key. He then places that in the pre-authentication data field of the request message. When the AS receives the message, it attempts to decrypt the authenticator using Alice's master key (which only it and Alice knows). If successful, the AS knows that the request came from Alice (and not from Larry or some other caller).

On Windows 2000, the pre-authentication data is required by default. So principals can only request a TGT for tickets that belong to them.

Now that all of that has been explained, you have the ticket-granting ticket, and you're ready to get a ticket to a specific secured resource. That's what the next sub-protocol is all about.

Ticket-Granting Service (TGS) Exchange

Once you have a ticket-granting ticket, you can obtain a ticket to a specific secured resource—recall this is what you were after in the first place. This is the purpose of the TGS Exchange.

The process is initiated by the caller when he sends a KRB_TGS_REQ message to the TGS service. The TGS processes this request and if successful, returns a KRB_TGS_REP to the caller (Figure 9).

Figure 9. Ticket-Granting Service message exchange

The KRB_TGS_REQ message contains more data that shown here but I've highlighted some of the more important information for the purposes of this discussion:

  • TGT + authenticator—The ticket-granting ticket grants us the ability to obtain tickets from the TGS. The authenticator (as explained above), proves the ticket has really come from the client (Quux\Alice). Note that this time the authenticator is encrypted using the client's session key—the one that the client uses to exchange data with the AS (not the master key).
  • Options—A request for options on how the ticket should be used (these are confirmed by the flags contained in the ticket in KRB_TGS_REP).
  • Client name & realm—The client sending the request (here, Quux\Alice)
  • Server Name—The server principal for which you're requesting the ticket (in this case, Quux\Bob).

The response contains two pieces of information. The first is a ticket for the server principal that was requested (in this case, a ticket for Quux\Bob). Note that this ticket is encrypted using Bob's master key so only Bob can decrypt it. The second piece of information is the session key that the client and server use to exchange data. This key is also encrypted but with the session key used between the client and the AS. This ensures that the client can decrypt it.

In the example, this means that Alice now has a ticket for Bob and she has the session key she'll use to exchange data with Bob. Now you're ready to call the server.

Client/Server (CS) Exchange

Once you have a ticket for a specific secured resource, you can securely send a message to it. This is the purpose of the CS Exchange.

The process is initiated by the client when he sends a KRB_AP_REQ message to the server. The server processes this request and optionally returns a KRB_AP_REP to the caller (Figure 10). The KRB_AP_REP message is used for mutual authentication

Figure 10. Client/server exchange messages

The KRB_AP_REQ message contains the following information:

  • Ticket (server principal)—This is the ticket granting you access to the server (in this case, Quux\Bob).
  • Authenticator—The authenticator that proves the identity of the caller (in this case, Quux\Alice). The authenticator is encrypted using the client/server session key (which the server discovers by decrypting the ticket sent to him).
  • Options—An optional request for mutual authentication. Note that the client doesn't specify this option This is a configuration parameter of Kerberos. If mutual authentication is requested, the KRB_AP_REP response must be returned to the client by the server.

The KRB_AP_REP is optional and is only returned if the caller requires mutual authentication. If this is the case, the server responds with a message containing the timestamp in the authenticator sent to him. This message is encrypted using the client/server session key. If the client successfully decrypts it, he knows that the response did in fact come from the server.

User2User

Now that all that has been explained, I'll complicate the issue slightly. Everything that has been explained so far is exactly the way things work on Windows 2000. However, if you run the accompanying sample on Windows XP or Windows Server 2003, you'll notice something peculiar. If you choose Kerberos as the authentication protocol (with no mutual authentication) you may notice that Kerberos doesn't authenticate in a single leg (as it does on Windows 2000). Instead, it requires a three-way handshake.

Why is the extra roundtrip required when the discussion of the Client/Server (CS) Exchange clearly indicates Kerberos can authenticate in a single leg? The answer is the User2User protocol.

Note   The single-leg authentication handshake is from the perspective of the client and server. The client side SSPI implementation still incurs roundtrips for the AS and TGS exchange (but that detail is hidden from the SSPI developer).

If you run the client executable (see the Sample Application section), and choose Kerberos with no authentication, you'll notice that an extra context attribute is selected on Windows XP or Windows Server 2003 but not on Windows 2000. This extra attribute is the Use Session Key attribute. The name of this attribute is somewhat of a misnomer. What it actually means is "use the User2User sub-protocol." This is standard on Windows XP and Windows Server 2003. Even if you don't ask SSPI for this option, you still get it. What this attribute adds in terms of external behavior is an extra roundtrip on the authentication handshake. Here's why.

The idea behind the User2User protocol is to protect the client and server involved in the authentication handshake from password cracking (mostly the server). Remember from the discussion of the Client/Server (CS) Exchange that the client passes a ticket for the server principal to the server. In the example this means that Alice acquires a ticket for Bob and sends that ticket to the server (along with an authenticator) as proof of her identity. Based on what has been discussed thus far, Bob's ticket is encrypted with Bob's master key (his password).

The User2User protocol recognizes that this might be a bad thing. The idea is that it is not optimal to provide a hacker with a piece of data encrypted with a long term key like a password. If a hacker stole this ticket off the wire, in theory he could work with it long enough to decrypt it. If he succeeded, he'd have Bob's master key (his password) and therefore could do anything as Bob.

User2User is a sub-protocol that modifies the three sub-protocols already discussed. When Alice sends the ticket for Bob to the server, User2User ensures that the ticket is NOT encrypted with Bob's master key but instead with Bob's session key with the Authentication Server (AS). This being the case, even if the hacker eventually decrypts Bob's ticket, all he's discovered is the short term session key that Bob has with the authentication service. In fact, the hacker might not even figure this out before Bob's session key expires. This greatly reduces any hacker's ability to compromise the server's credentials. The question is, now does Alice acquire a ticket encrypted with Bob's session key with the AS? Let's go back and explain User2User from the beginning.

The process begins for Alice after the Authentication Server (AS) Exchange. At this point Alice has established her session key with the Authentication Server and is in possession of a TGT. At this point, Alice would normally move along to the Ticket-Granting Service (TGS) Exchange. But User2User calls for a diversion.

At this point, Alice makes an exchange with Bob. This begins where Alice forms a KERB_TGT_REQUEST message. This request contains an optional server name and realm. Essentially, this is just a message to kick off the protocol.

The response that Alice receives from Bob is a KERB_TGT_REPLY. This message contains the ticket-granting ticket for Bob (Figure 11).

Figure 11. Ticket-granting ticket for Bob

Now that Alice has Bob's TGT, she can continue with the Ticket-Granting Service (TGS) Exchange. The difference is that when Alice sends the KRB_TGS_REQ message, she includes the option ENC_TKT_IN_SKEY along with Bob's TGT ticket. The Ticket-Granting Service then cracks open Bob's TGT and uses the enclosed session key to encrypt a ticket for Bob. This new ticket is returned to Alice in the KRB_TGS_REP message.

The rest of the authentication process is the same. Alice then continues with the Client/Server (CS) Exchange where she passes the ticket for Bob to the server along with her authenticator.

The original one-leg authentication is now a three way handshake between Alice and Bob. And at no time are you passing tickets on the wire that are encrypted using long term keys (passwords).

As with the rest of the discussion on the Kerberos sub-protocols, you don't always need to have all of this detail at the forefront of your mind. But when you try out the sample and you're wondering where the extra roundtrip in the authentication handshake is coming from, User2User is the answer.

Recap

With what you now know you can:

  • Authenticate the client to the server
  • Ensure message integrity
  • Encrypt messages
  • Delegate (I include some references at the end of this article that explains delegation in detail.)

Note that as with NTLM, clear text credentials are never sent across the wire yet you can prove to a server who a caller is. Also note that while NTLM requires a three leg handshake, in Kerberos, once you have a TGT, authentication with a secured resource can be handled in a single roundtrip (in the absence of User2User). In Kerberos, the cost of authentication is spread among the three primary sub-protocols which buys us some efficiency.

Now you just have to learn how to actually use these protocols.

SSPI

Now that the details of NTLM and Kerberos are clear, you need some way to actually make use of them. After all, I haven't described any APIs for programming against these authentication protocols, just the protocols themselves and how they work in the abstract.

To make these protocols usable, it might make sense to create a Kerberos API and an NTLM API. Each would allow the user to process messages particular to their protocol as well as perform common operations such as signing or encrypting messages. While this would work, it would be cumbersome and it would mean having a programming library for every authentication protocol used. Fortunately, there's a solution in Windows that allows us to use all of these authentication protocols using a single API—the SSPI.

SSPI stands for Security Support Provider Interface. It is based on the Generic Security Service API (GSSAPI) described in RFCs 1508 and 1509. It abstracts the implementation details of different authentication protocols and provides a single, common programmatic interface.

SSPI allows developers to perform authentication functions independent of the actual authentication protocol chosen. SSPI is also independent of the communications protocol used; it can and is used with RPC, TCP sockets, DCOM, and so on. The basic idea is that each communication protocol provides extra space in their message packets for opaque tokens passed between SSPI implementations. The communication protocol knows nothing about any authority involved (that is between the SSPI implementation and the authority); the SSPI implementation takes care of the rest.

The architecture of the SSPI model is based on the notion of an SSP (Security Support Provider). The SSP is a DLL that implements the SSPI interface and makes one or more security packages available to applications. So, the idea is that there's an SSP for NTLM, one for Kerberos, and so on.

Distributed application developers who leverage security use SSPI all the time and probably don't know it. SSPI is leveraged by COM, RPC, Windows Sockets 2.0, WinInet, among others. Most of the time you don't program against SSPI directly. However, in this case you're building a managed assembly around SSPI that you can employ in a .NET remoting security solution. You must program directly against SSPI to make that work.

To understand some of the complexities involved in providing a single API for multiple authentication protocols, look at an example of the NTLM and Kerberos handshake that you've already seen (Figure 12).

Figure 12. NTLM and Kerberos handshake

NTLM has a three-way handshake between client and server. Kerberos only has a two-way handshake, however there's more communication to the authority in Kerberos than in NTLM (assuming mutual authentication without User2User).

There are commonalities between NTLM and Kerberos that can be exploited: both result in a client and server session key; both support signing and encryption. Mechanically, the major differences are really just in the roundtrips required between client and server to complete the authentication handshake. SSPI abstracts out these commonalities and allows for the differences.

To make all of this more concrete, I'll go over some of the more important APIs in the Security Support Provider Interface. This should make the protocols discussed in the previous section somewhat more clear.

SSPI in a Nutshell

In terms of the details covered for NTLM and Kerberos, there are three functional areas that SSPI provides:

  • Authentication—Completing the authentication handshake between client and server such that the result is a session key at both ends.
  • Impersonation—Used on the server side for impersonating the client and reverting to the original call context.
  • Signing/Encryption—Used on the client and server side to exchange signed and encrypted messages.

I'll provide a brief overview of how each of these work in SSPI.

Authentication

To get things kicked off, both client and server call AcquireCredentialsHandle(). This API initializes the caller's session and allows the caller to choose a security package (SSP) that it wishes to use.

At this point, the client calls InitializeSecurityContext(). The name of the API is somewhat misleading because it may actually be called multiple times. It is called the first time to generate a security token to pass to the server. By security token, I mean an opaque message that is sent to the server for processing (for NTLM, this initial security token is a negotiate message; for Kerberos it may be a KRB_AP_REQ message).

When the server receives the security token from the client, it passes it to AcceptSecurityContext(). This processes the incoming message and generates a new one (depending on the security package) to pass back to the client. For NTLM, a new security token is generated which contains the challenge message.

At this point the client would again call InitializeSecurityContext() on the security token returned from the server. If that call generates a new token, it is passed to the server once again. For NTLM, a new token is generated which contains the response message.

This process continues on both sides until InitializeSecurityContext() and AcceptSecurityContext() return a code indicating that they're done. Baring any errors, "done" means that the authentication handshake is complete and what's left is a session key (and a new network logon session on the server side) that can be used to perform post-authentication functions.

Impersonation

Once the authentication handshake is complete, the server side can optionally call ImpersonateSecurityContext(). This places a token for the client's network logon session on the current thread (an impersonation token). At this point, the server can do work with the caller's credentials.

If the server wishes to undo the effects of the impersonation, he calls RevertSecurityContext(), which removes the impersonation token and returns the security context to its state before ImpersonateSecurityContext() was called.

The server can also call QuerySecurityContextToken() without impersonating the client. This allows the server to get a reference to the token for the client's network logon session. This saves the server the trouble of having to impersonate the caller, access the thread token, and then revert the impersonation.

Signing/Encryption

Finally, both the client and server can leverage their session keys to sign and encrypt messages that are sent to one another.

Signing is achieved by calling MakeSignature() to create the Message Authentication Code (MAC) to attach to a message. A signature is verified by calling VerifySignature().

Likewise, encryption is achieved by calling EncryptMessage(). An encrypted message is turned back into its clear text form by calling DecryptMessage().

Any of the signing/encryption APIs can be called on either the client or server side. The client can sign and encrypt messages for the server and visa versa. The only requirement is that the session key is available (which is true any time after the authentication handshake is complete).

A Word about SPNEGO

I mentioned that to get the authentication handshake kicked off, the client and server both call AcquireCredentialsHandle(). In doing so both sides have to specify the same security package (NTLM or Kerberos). Actually, there's a special security package you can specify that automatically negotiates the best security package to use. This is called SPNEGO.

SPNEGO stands for Secure and Protected Negotiation. Like Kerberos, it's a well-documented protocol defined in RFC 2478. SPNEGO basically addresses the case where a client and server want to authenticate but they support more than one authentication protocol. SPNEGO allows the most secure protocol to be chosen automatically and does so securely.

The idea of how SPNEGO works is simple. Say, for example, that both the client and server choose the SPNEGO protocol and they're both running on Windows 2000 (which supports both NTLM and Kerberos). When the client creates the first security token to pass to the server, is it a NTLM negotiate message or a Kerberos KRB_AP_REQ message? SPNEGO chooses the most secure protocol available and generates a token for that protocol (in this case, Kerberos is considered more secure). So in the example, this means the client would generate a KRB_AP_REQ message. The server in this case is also running Windows 2000 so it can accept the KRB_AP_REQ message. If the server could not (maybe it only understood NTLM), extra roundtrips would be required to resolve the mismatch. But in the case where the server understands the message, no extra roundtrips are required. This mean SPNEGO does not carry an extra roundtrip cost relative to specifying an authentication protocol explicitly and it makes the implementation more flexible.

SPNEGO is the default provider for most communication subsystems on Windows 2000—including COM.

SSPI in Microsoft.Samples.Security.SSPI

I've explained some of the more important APIs in the Security Support Provider Interface. For an overview of which portions of the interface I include in the managed assembly you may refer to Table 1.

Table 1. SSPI usage in Microsoft.Samples.Security.SSPI

Package Management
EnumerateSecurityPackages
InitSecurityInterface
QuerySecurityPackageInfo
Credential Management
AcquireCredentialsHandle
ExportSecurityContext
FreeCredentialsHandle
ImportSecurityContext
QueryCredentialsAttribute
Context Management
AcceptSecurityContext
ApplyControlToken
CompleteAuthToken
DeleteSecurityContext
FreeContextBuffer
ImpersonateSecurityContext
InitializeSecurityContext
QueryContextAttributes
QuerySecurityContextToken
RevertSecurityContext
Message Support
DecryptMessage
EncryptMessage
MakeSignature
VerifySignature

As you can see, the API is not terribly large and in fact most of the API is leveraged in the Microsoft.Samples.Security.SSPI assembly (those APIs that aren't are rendered with a line drawn through them).

Remember the main intent is to build a managed SSPI library that supports security in .NET remoting. Anything that didn't directly support this was left out.

Recap

  • You now have a concrete, programmatic way to authenticate a client to a server, sign, and encrypt messages
  • The API gives us a protocol independent method of authentication—just pass opaque tokens back and forth until authentication succeeds or the client and server SSPI says to stop
  • You have authentication protocol negotiation through SPNEGO

Microsoft.Samples.Security.SSPI

It is clear that the Security Support Provider Interface provides us the basic functionality needed for a remoting security solution. SSPI provides us the ability to authenticate a client and a server, sign/encrypt messages, as well as impersonate a client—all of the things needed in the solution. But since the security solution is being written in managed code, you'd like a managed version of the SSPI.

So how do you go about building a managed version of the Security Support Provider Interface? One approach is to dismiss the idea of building a managed version and use SSPI directly with PInvoke. While this is an alternative, there are issues here that stand in the way. First, there are a lot of complex data structures used in SSPI function parameters that are complex and difficult to use in a managed language. Also, despite the fact that SSPI is itself an abstraction layer, there are still some details that you can weed out if you built your own version. Finally, since you're writing the remoting security sample in managed code, it would be nice to leverage an SSPI library that's already managed.

Based on all of these factors, I decided to go ahead and write a managed version of the SSPI interface—one written in Managed C++. I bundled the solution into a single assembly called Microsoft.Samples.Security.SSPI.

Why not write it in C#?   I experimented with writing the assembly in C# as well as Managed C++. I quickly discovered a basic trade-off: did I want to work with unmanaged code like the SSPI in a managed language (like C#) or did I want to write it in C++ and use managed extensions to interface to the .NET world?
With SSPI there are a number of functions with complex parameters. This includes lots of pointers, (nested) structures, and so on. Writing the assembly in C# means that having to declare managed versions of the SSPI function parameters (all of those complex types) and then making sure they're properly pinned in memory when passing them to the unmanaged functions.
Writing the assembly in managed C++ means dealing with the unmanaged types in their native environment. You don't necessarily have to worry about pinning and you don't have to re-declare types (just pull in the appropriate header files).
When building Microsoft.Samples.Security.SSPI, I found it was easier to do the job in Managed C++. All of the complex types are kept in the unmanaged world; the unmanaged APIs are dealt with in their native C environment. Managed C++ provides a great way to bridge the gap between the unmanaged/managed world.
Your mileage may vary and choice is going to depend on the APIs being used. But with SSPI, managed extensions make more sense than a higher level language like C#.

So you've decided to write an assembly. What should it look like? Consider what you really want out of the managed SSPI library:

  • Authentication—Functions that generate and process the authentication messages sent between client and server, independent of a particular security package
  • Impersonation—The ability of a server to take on a client's credentials
  • Signing/Encrypting—Adding a signature or completely encrypting messages sent between client and server

This is a different organization than taken by SSPI. But thinking about it this way gives us an opportunity to rethink the design and maybe come up with something simpler. In fact, it's instructive to look at this from the developer's point of view.

Credential

You know from studying the SSPI that the first thing a client or server does when authenticating is call AcquireCredentialsHandle(). This function has a lot of gory details to its function parameters, but fundamentally all you're interested in using this function for is to choose the security package (NTLM, Kerberos, or Negotiate). So you know you need to create a credentials handle, and when you do, you specify the security package to be used.

So let's start the managed SSPI library by saying a Credential object is needed, and when one is created, the security package associated with it will be identified. A client and server side needs a ClientCredential and a ServerCredential class created (Figure 13).

Figure 13. A ClientCredential and a ServerCredential class

You'll implement the notion that you can't have a credential without a security package by parameterizing the constructor and passing in the enumeration for the security package.

The credential has properties that allow you to retrieve various information about the credential:

  • SecurityPackage—The enumeration value of the security package you passed to the constructor
  • CredentialHandle—The underlying SSPI credential handle
  • Name—The principal name associated with the credential. In SSPI you can attach specific credentials to an SSP when you call AcquireCredentialsHandle() or you can use the credentials of the caller. In Microsoft.Samples.Security.SSPI the caller's credentials are always used. So Name is the principal name of the caller.

Programmatically, making use of the ClientCredential and ServerCredential classes is straightforward:

// client
ClientCredential clientCred =
    new ClientCredential(Credential.Package.NTLM);

// server
ServerCredential serverCred =
    new ServerCredential(Credential.Package.NTLM);

So far so good. Initiating use of the managed library means you have to create a Credential object and pass in the name of the security package. This is far easier than calling AcquireCredentialHandle(). The object-oriented nature also means that you can easily access properties on the object in a way that was impossible using SSPI.

You're not done yet. You still have to provide authentication services to the users of the class library. Again, you can use SSPI as a guide.

Context

If you were to continue writing the solution using SSPI, the next step after acquiring the credential handle would be to call InitializeSecurityContext() or AcceptSecurityContext(). So after you build a credential you have this notion of building a context. Once again, there's a lot of gory detail to the parameters of these functions. But fundamentally, InitializeSecurityContext() takes the client credential handle created in AcquireCredentialsHandle() and returns a security token to send to the server. Similarly, AcceptSecurityContext() takes a server credential handle created in AcquireCredentialsHandle() and a security token created by the client. Depending on the security package used, it creates a security token to send back to the client.

So in the managed library a notion of context will be created: a ClientContext class as well as a ServerContext class. It will only be possible to create a ClientContext object if a ClientCredential has previously been created. Likewise, it will only be possible to create a ServerContext if a ServerCredential has previously been created (Figure 14).

Figure 14. Client Context and Server Context

You can implement the notion that you can't have a context without first creating a credential by parameterizing the constructor and passing in the credential that you already created.

Note that the ClientContext constructor has additional parameters that the ServerContext constructor does not have: ServerPrincipal and ContextAttributeFlags. ServerPrincipal is required by Kerberos or Negotiate and is the name of the server principal you're authenticating with. ContextAttributeFlags specifies any additional requirements that the caller demands of the new ClientContext. Today there's only one flag: Delegate (supported only by Kerberos).

The client and server context both share common methods between them:

  • SignMessage—Takes a clear text message as an argument and returns its signed equivalent
  • VerifyMessage—Takes a signed message as an argument and after verification turns it back into the original clear text message
  • EncryptMessage—Takes a clear text message as an argument and returns its encrypted equivalent
  • DecryptMessage—Takes an encrypted message as an argument and turns it back into the original clear text message.

Each of the signing/encryption functions has very simple signatures and they each perform their job for whichever security package is attached to the context.

The ClientContext has one method that's unique to it:

  • Initialize—Takes a security token as an argument and processes that token. If it generates a token to return to the server, that token is returned in the Token property.

The ServerContext has several unique methods:

  • Accept—Takes a security token from the client and processes it. If it generates a token to return to the client, that token is returned in the Token property.
  • ImpersonateClient—Sets the thread token to the one referring to the client's logon session.
  • RevertImpersonation—Removes the thread token, undoing the effect of ImpersonateClient.

Both client and server contexts have the following properties in common:

  • Credential—Reference to the credential passed into the constructor.
  • ContextAttributes—Attributes associated with the context.
  • Name—Principal name associated with the context.
  • SecurityPackage—Enumeration of the security package associated with the context.
  • Token—For the ClientContext, the security token that should be sent to the server; for the ServerContext the security token that should be sent to the client. This is the authentication message that's particular to the security package used. For example, in NTLM this would be the Negotiate, Challenge, or Response message.
  • ContinueProcessing—A Boolean informing the caller whether they should continue exchanging authentication messages (so, for NTLM, this property becomes false on the ServerContext object after it processes the Response message from the client).

There are no additional properties on the client side but on ServerContext there is one unique property:

  • ClientSecurityToken—A handle to the Win32 security token of the client.

Authentication

Now that most of the class library is in place, it can be used. Start by authenticating the client to the server. Programmatically, this can be done on the client side with the following code:

//
// Client side authentication code
//
ClientContext clientContext =
   new ClientContext(clientCred, "",
      ClientContext.ContextAttributeFlags.None); 


// send the authenticate request to the server
Byte[] serverToken = 
   SendAuthenticationMessageToServer(clientContext.Token);



// complete the authentication handshake by exchanging messages
// with the server until ContinueProcessing returns false
while (clientContext.ContinueProcessing == true)
{
    clientContext.Initialize(serverToken);
    if (clientContext.Token != null)
        serverToken =
            SendAuthenticationMessageToServer(clientContext.Token);
}

And on the server side:

// 
// Server side authentication code
// 

private ServerContext _ServerContext = null;

public Byte[] ReceiveAuthenticationMessageFromClient(
                  Byte[] inToken)
{
// create a context if doesn't already exist otherwise just
// accept the given security token
if (_ServerContext == null)
    _ServerContext = new ServerContext(serverCred,   
                                    authenticateRequest);
else
    _ServerContext.Accept(inToken);


// return the token to the caller
return _ServerContext.Token;
}

Authenticating a client starts on the client side. There you create a ClientContext object and pass the generated security token to the server. The process continues on the server side which creates a ServerContext (the first time through) or accepts the incoming token (for multi-leg handshakes). All of this continues until the ContinueProcessing property becomes false. This scheme supports any authentication package (NTLM, Kerberos, or Negotiate). This is true regardless of the fact that each protocol is sending different messages to one another and each requires a different number of roundtrips.

The code required to authenticate using Microsoft.Samples.Security.SSPI is far simpler than calling InitializeSecurityContext() and AcceptSecurityContext() using SSPI directly. A number of properties on the Context object can be accessed that cannot be accessed in the non-object-oriented SSPI.

Impersonation

Now that the client has been authenticated, impersonation is even easier.

//
// Server side
//

// impersonate the client
_ServerContext.ImpersonateClient();


//
// ToDo: Some work with the client's credentials
//


// revert to self
_ServerContext.RevertImpersonation();

Impersonation is trivial. Just call ImpersonateClient() to switch to the client's credentials. Once you're finished doing work as the client, you can revert by calling RevertImpersonation().

Signing and Encryption

Signing and encryption is just as easy. On the client side the code looks like this:

//
// Client side
//

public void SignAndEncryptMsgs(Byte[] msg1, Byte[] msg2)
{
    // sign a message
    Byte[] signedMsg = clientContext.SignMessage(msg1);

    // encrypt a message
    Byte[] encryptedMsg = clientContext.EncryptMessage(msg2);

    // send the messages off to the server
    SendMsgsToServer(signedMsg, encryptedMsg);
}

And on the server side:

//
// Server side
//

public void ReceiveSignedAndEncryptedMsgs(Byte[] signedMsg, 
                                          Byte[] encryptedMsg)
{
    // verify a message
    Byte[] verifiedMsg = _ServerContext.VerifyMessage(signedMsg);

    // decrypt a message
    Byte[] decryptedMsg = _ClientContext.DecryptMessage(encryptedMsg);
}

Signing or encrypting is easy. Just pass in your clear text message and you get back a signed or encrypted version. Verification and decryption works virtually the same: pass in your signed or encrypted message and you get back your original clear text message.

Security Packages

There's one more part of Microsoft.Samples.Security.SSPI that hasn't been discussed yet: the SecurityPackages class (Figure 15).

Figure 15. The SecurityPackages class

SecurityPackages has a single static method called GetAllSecurityPackages(). As the name implies it returns a list of all of the security packages available on the local machine. Note that if this is called on Windows 2000 or later, you'll see NTLM, Kerberos, and Negotiate; however, you'll see a list of many others as well. While these other protocols are available on your machine and supported by SSPI, they are not supported by Microsoft.Samples.Security.SSPI. Again, this is a security solution for .NET remoting; NTLM, Kerberos, and Negotiate are the only protocols needed to target for this.

Recap

  • You now have a managed version of the SSPI library.
  • Most (but not all) of the original functionality found in SSPI is included in the managed version.
  • The managed library is dirt simple to use (compare some of the code in this section to some native SSPI code that does the same thing).

Sample Application

In the last section, I include some code snippets that demonstrate basic use of the Microsoft.Samples.Security.SSPI assembly, but I wanted to provide a more comprehensive sample that shows off its full functionality. This section explains a little about that sample.

The sample application consists of two .NET Winforms applications. The applications happen to communicate with one another through .NET remoting (each application hosts a .NET component which the other remotely accesses). The use of .NET remoting in this sample is just a convenience as it handles the marshaling and lot of other details of passing messages back and forth between the two applications. I could have replaced .NET remoting with sockets, MSMQ, RPC, and so on. and the application still would have worked.

When you start the client, the main form in the application is displayed (Figure 16).

Click here for larger image

Figure 16. Sample client (click thumbnail for larger image)

The client is built to walk the user through the process of authenticating with a server. You'll notice when you first start the client that only the first section "Create Credential" is enabled. This is because creating a credential is the first thing you'll do. After you press the Create button, the first section will be disabled and the second section "Create Context" will be enabled. This will continue until the client has authenticated with the server.

Note also the "Transmit and Receive" section. These are the parameters that the client and server use to communicate with one another. By default, both the client and server run on the same machine. The client listens on port 9000 and the server on port 9001. This can be changed by altering the parameters in this section.

Basically the client allows the user to choose a security package (NTLM, Kerberos, or Negotiate) when creating a credential. It leads you through creating a Context (if you choose Kerberos or Negotiate you'll need to specify a server principal name). Finally, you perform the authentication handshake with the server. Once that's done the client and server can send messages to one another (clear text, signed, or encrypted).

Starting the server application shows a similar form (Figure 17).

Click here for larger image

Figure 17. The server application

Like the client, the Server starts by allowing the user to chose a security package and create the ServerCredential. At this point, the behavior starts to diverge from the client.

Once the credential is created, the server waits for the client to initiative the authentication handshake. Once that's done, the server can Impersonate the client (and also revert to the original security context). The user can even view details of the impersonation token.

The readme included in the sample contains a detailed walkthrough of the client and server.

Summary

In the introduction to this article I discussed some of the security implications of remoting a simple component from a client. The goal was to build a .NET security solution that made the remoted version "act like" the local one. You're not there yet, but the SSPI assembly described in this article gets you most of the way there.

Microsoft.Samples.Security.SSPI gives us a managed version of the SSPI library. You can now authenticate a client to a server, impersonate the client, and sign/encrypt messages. This assembly even rethinks the SSPI design and brings it into an object oriented world. Performing equivalent functions with Microsoft.Samples.Security.SSPI is far easier and less painful than using SSPI directly. Since it's managed, it can be used from any .NET programming language, and from a security perspective all of the major authentication protocols (namely, NTLM, Kerberos, and Negotiate) can be accessed.

Essentially, Microsoft.Samples.Security.SSPI performs a lot of the heavy lifting needed to build a security solution for .NET remoting. The key thing it lacks is transparency. If you want to use it to implement authentication, you need to explicitly program against it—mindfully creating, processing, and passing security tokens between client and server.

In the next article I'll explain another assembly that provides a security solution specifically for .NET remoting. It leverages Microsoft.Samples.Security.SSPI to provide a security solution that is transparent to the user (they'll never have to worry about security tokens). And the final solution is so accessible, using it is just a matter of configuration—no coding necessary.

References

Programming Windows Security by Keith Brown (Addison-Wesley 2000)

Security Briefs: Explore the Security Support Provider Interface Using the SSPI Workbench Utility

Security Briefs: The Security Support Provider Interface Revisited

Brown, Keith. Token Dump Component. March 2001.

RFC 1508: Generic Security Service Application Program Interface

RFC 1509: Generic Security Service API: C-bindings

RFC 1510: The Kerberos Network Authentication Service (V5)

RFC 2478: The Simple and Protected GSS-API Negotiation Mechanism

Swift, M.; Moore, P.; Brezak, J. Kerberos Working Group Internet Draft—draft-swift-win2k-user2user-04.txt. April 2002

Microsoft Platform SDK

Microsoft .NET Framework Software Development Kit

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.