Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RequestSecurityToken using windows credentials and .net 4.5 WIF

Can anyone point to sample code for actively issuing a RequestSecurityToken using the NT credentials of the Thread.CurrentPrincipal as ClaimsPrincipal?

The scenario is an asp.net web app with windows authentication enabled (so there is an authenticated WindowsIdentity). My desire is to call the STS actively rather than enabling passiveRedirect, and to do this using the .Net 4.5 identity libraries.

Most code samples, such as Claims Helper for Windows Phone or Using an Active STS set the credentials with a username/pwd input and UserNameWSTrustBinding.

I thought the solution might involve impersonation or a call to channelFactory.CreateChannelWithActAsToken() with the a token created from the windows identity.

-- The following .Net4.5 code does get a GenericXmlSecurityToken when hitting an /adfs/services/trust/13/windowsmixed endpoint. However, the claims are for the domain account under which the site is running, and not the domain account of the authenticated user. When I switch the endpoint to /adfs/services/trust/13/kerberossmixed, I get "cannot negotiate" errors as documented in several questions and forums, but I cannot apply any offered solutions with .Net 4.5. One of the classes not ported over from Microsoft.IdentityModel is the KerberosWSTrustBinding...

public static void CallSts()
{
    try
    {
        var wsMod = FederatedAuthentication.WSFederationAuthenticationModule;
        var appliesToEp = new EndpointReference(wsMod.Realm);
        var stsEp = new EndpointAddress(new Uri(wsMod.Issuer), EndpointIdentity.CreateSpnIdentity("stsSpn"));

        var msgBinding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential, false);
        msgBinding.Security.Message.EstablishSecurityContext = false;
        msgBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

        using(var factory = new WSTrustChannelFactory(msgBinding, stsEp))
        {
            factory.Credentials.SupportInteractive = false;
            factory.TrustVersion = TrustVersion.WSTrust13;

            var myRst = new RequestSecurityToken
            {
                RequestType = RequestTypes.Issue,
                AppliesTo = appliesToEp,
                KeyType = KeyTypes.Bearer,
            };
                var channel = factory.CreateChannel();
                var stsToken = channel.Issue(myRst) as GenericXmlSecurityToken;

                if(stsToken != null)
                {
                    Log.DebugFormat("Reply Token is {0}", stsToken.GetType().Name);

                    var handlers = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlers;
                    var token = handlers.ReadToken(new XmlTextReader(new StringReader(stsToken.TokenXml.OuterXml)));
                    var identity = handlers.ValidateToken(token).First();
                    //TODO write to session
                }
                else
                {
                    Log.Debug("Reply Token is null.");
                }
        }
    }
    catch(Exception ex)
    {
        Log.Error("Rst.Call has failed", ex);
    }
}

For @leastprivilege suggestion, I add this code:

    var user = Thread.CurrentPrincipal as ClaimsPrincipal;
var winId = user.Identity as WindowsIdentity;
if(winId != null)
{
    // shows my domain account after I was prompted for credentials;
    // my domain account does not exist on the client machine, so it is a true domain credential
    Log.DebugFormat("WindowsIdentity Name is {0}", winId.Name);
}
using(winId.Impersonate())
{
    // again, shows my domain account
    Log.DebugFormat("Impersonation Context {0}", WindowsIdentity.GetCurrent(true).Name);
    var channel = factory.CreateChannel();
    var stsToken = channel.Issue(myRst) as GenericXmlSecurityToken;
    // request is issued, but results in SecurityNegotiationException: The caller was not authenticated by the service.
}

Which fails with "The caller was not authenticated by the service". The same STS will authenticate my domain account in a passive redirect scenario...so although I know I am doing something wrong, the account itself should be recognized.


Update:

I received a notification that this question received a notable number of views, so I will offer the following as one workaround: Although we configured our servers for delegation (as Dominick suggested below), we still did not surmount the double-hop issue. If I remember, we hit a roadblock from simple network management policies above our local IT that any enterprise might hit as well. So, while impersonating over a double-hop against a server with Windows Authentication is not allowed, credentials can be impersonated over a double hop using Basic Authentication. This may or may not be an acceptable situation (intranet for our case). If you do, you would add

msgBinding.Security.Message.NegotiateServiceCredential = true;

to the above ChannelBinding configuration.

like image 634
mdisibio Avatar asked Jan 09 '13 23:01

mdisibio


1 Answers

Well - This is actually not trivial. You need to do Kerberos impersonation and delegation for that.

First of all impersonation. You need to call Impersonate() on the WindowsIdentity you get from Thread.CurrentPrincipal.

You can make sure you are impersonating by calling WindowsIdentity.GetCurrent. This identity must point to the client then (as opposed to the server identity).

Then while impersonating you need to make the WS-Trust request. This is most probably not allowed by default. So you network admin needs to configure delegation for the sever identity to the STS.

like image 186
leastprivilege Avatar answered Nov 09 '22 01:11

leastprivilege