My diary of software development

Archive for March, 2010

Finally! An 4.0 XBAP can call a Windows Authenticated WCF Service! Part 2

In the first part to this subject, I wrote about the problem I ran into, the hotfix from Microsoft, the hotfix’s failure, and finally Microsoft’s promise to fix the bug in .Net 4.0. Now that 4.0 is in RC mode and proved itself to fix this problem, I’m going to take a look at the source code to see just how it did fix this problem. But before I look at the 4.0 source code, I’m going to look at the 3.5 pre-hotfix source code and then the 3.5 post hotfix source code to get a feel for what Microsoft was doing which caused my problem. By the way, I’m using RedGate’s Reflector product to look at the source code.   

.Net 3.5 Service Pack 1, Pre-Hotfix

Here is the exception stack thrown when my problem first surfaced in the 3.5 pre-hotfix era:

Pre-Hotfix Exception

 The exception came from HttpChannelFactor.GetConnectionGroupName():   

 

[SecurityTreatAsSafe, SecurityCritical]
private string GetConnectionGroupName(HttpWebRequest httpWebRequest, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel, SecurityTokenContainer clientCertificateToken)
{
    if (this.credentialHashCache == null)
    {
        lock (base.ThisLock)
        {
            if (this.credentialHashCache == null)
            {
                this.credentialHashCache = new MruCache<string, string>(5);
            }
        }
    }
    string inputString = TransferModeHelper.IsRequestStreamed(this.TransferMode) ? "streamed" : string.Empty;
    if (AuthenticationSchemesHelper.IsWindowsAuth(this.AuthenticationScheme))
    {
        httpWebRequest.UnsafeAuthenticatedConnectionSharing = true;
        inputString = this.AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel);
    }
    inputString = this.OnGetConnectionGroupPrefix(httpWebRequest, clientCertificateToken) + inputString;
    string str3 = null;
    if (!string.IsNullOrEmpty(inputString))
    {
        lock (this.credentialHashCache)
        {
            if (!this.credentialHashCache.TryGetValue(inputString, out str3))
            {
                byte[] bytes = new UTF8Encoding().GetBytes(inputString);
                str3 = Convert.ToBase64String(this.HashAlgorithm.ComputeHash(bytes));
                this.credentialHashCache.Add(inputString, str3);
            }
        }
    }
    return str3;
}

The GetConnectionGroupName() function is attempting to set the UnsafeAuthenticatedConnectionSharing property and according to the MSDN reference for the property, an application must have unrestricted web permission to set it. However I used the Intranet Zone permission set for my XBAP which did not have the web permission security privlege and so this is why the above exception was thrown. I did not dig into the actual source code when this problem arose last summer, I simply opened a ticket with Microsoft because I figured that I should be able to call a WCF service secured with Windows Integrated security from an XBAP. After a few days, I received feedback from Microsoft that they had released a hotfix.   

.Net 3.5 Service Pack 1, Post-Hotfix

Here is how Microsoft fixed the above problem in the hotfix. The below code is the same HttpChannelFactory.GetConnectionGroupName() function shown above except that it has the hotfix changes in it:    


[SecurityCritical, SecurityTreatAsSafe]
private string GetConnectionGroupName(HttpWebRequest httpWebRequest, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel, SecurityTokenContainer clientCertificateToken)
{
    if (this.credentialHashCache == null)
    {
        lock (base.ThisLock)
        {
            if (this.credentialHashCache == null)
            {
                this.credentialHashCache = new MruCache<string, string>(5);
            }
        }
    }
    string inputString = TransferModeHelper.IsRequestStreamed(this.TransferMode) ? "streamed" : string.Empty;
    if (AuthenticationSchemesHelper.IsWindowsAuth(this.AuthenticationScheme))
    {
        if (!httpWebRequestWebPermissionDenied)
        {
            try
            {
                httpWebRequest.UnsafeAuthenticatedConnectionSharing = true;
            }
            catch (SecurityException)
            {
                httpWebRequestWebPermissionDenied = true;
            }
        }
        inputString = this.AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel);
    }
    inputString = this.OnGetConnectionGroupPrefix(httpWebRequest, clientCertificateToken) + inputString;
    string str3 = null;
    if (!string.IsNullOrEmpty(inputString))
    {
        lock (this.credentialHashCache)
        {
            if (!this.credentialHashCache.TryGetValue(inputString, out str3))
            {
                byte[] bytes = new UTF8Encoding().GetBytes(inputString);
                str3 = Convert.ToBase64String(this.HashAlgorithm.ComputeHash(bytes));
                this.credentialHashCache.Add(inputString, str3);
            }
        }
    }
    return str3;
}

The httpWebRequestWebPermissionDenied flag is checked to guard against setting the UnsafeAuthenticatedConnectionSharing property. So if this flag is set, the code will not try to set the connection sharing property. However, where is the httpWebRequestWebPermissionDenied flag set? It is set to true if an exception is thrown in the above code on line 25 and it is initialized to false in the constructor but the only other place it is set is in the OnOpen() function:    


protected override void OnOpen(TimeSpan timeout)
{
    if (this.IsSecurityTokenManagerRequired())
    {
        this.InitializeSecurityTokenManager();
    }
    if (this.AllowCookies)
    {
        this.cookieContainer = new CookieContainer();
    }
    if (!httpWebRequestWebPermissionDenied && (HttpWebRequest.DefaultMaximumErrorResponseLength != -1))
    {
        int num;
        if (this.MaxBufferSize >= 0x7ffffbff)
        {
            num = -1;
        }
        else
        {
            num = this.MaxBufferSize / 0x400;
            if ((num * 0x400) < this.MaxBufferSize)
            {
                num++;
            }
        }
        if ((num == -1) || (num > HttpWebRequest.DefaultMaximumErrorResponseLength))
        {
            try
            {
                HttpWebRequest.DefaultMaximumErrorResponseLength = num;
            }
            catch (SecurityException exception)
            {
                httpWebRequestWebPermissionDenied = true;
                if (DiagnosticUtility.ShouldTraceWarning)
                {
                    DiagnosticUtility.ExceptionUtility.TraceHandledException(exception, TraceEventType.Warning);
                }
            }
        }
    }
}

 

This function uses pretty much the same logic to set the httpWebRequestWebPermissionDenied flag. If a SecurityException is thrown, then the flag is set to true. That seems like a quick and dirty way to set the permission flag’s value and I guess it works but it seems to me that it should be set during class initialization by reading the XBAP’s associated manifest file to see if that permission is in the XBAP’s permission set. I looked at the .Net 4.0 code to see if the flag was initialized in some way but found that it uses the same logic to figure out if the XBAP doesn’t have web permission. Strange, I thought there’d be a more elegant way.    

However ugly the above hotfix is, it did get the code past that point and on down the callstack. But farther down in the callstack another exception was thrown:    

Post-Hotfix Exception

This next exception was thrown because SecurityUtils.AppendWindowsAuthenticationInfo() called WindowsIdentity.GetCurrent():    


[SecurityCritical]
internal static string AppendWindowsAuthenticationInfo(string inputString, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel)
{
    if (IsDefaultNetworkCredential(credential))
    {
        using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
        {
            SecurityIdentifier user = identity.User;
            return (inputString + "\0" + user.Value + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
        }
    }
    return (inputString + "\0" + NetworkCredentialHelper.UnsafeGetDomain(credential) + "\0" + NetworkCredentialHelper.UnsafeGetUsername(credential) + "\0" + NetworkCredentialHelper.UnsafeGetPassword(credential) + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
}

However, in order to call WindowsIdentity.GetCurrent, the app must have SecurityPermissionFlag.ControlPrincipal as the WindowsIdentity.GetCurrent() method demands it:    


[SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.ControlPrincipal)]
public static WindowsIdentity GetCurrent()
{
    return GetCurrentInternal(TokenAccessLevels.MaximumAllowed, false);
}

Since my clickonce XBAP did not have this security, this Demand caused another exception.    

.Net 4.0

The .Net 4.0 code was the same as the hotfix code for dealing with the UnsafeAuthenticatedConnectionSharing property problem. However it handled the WindowsIdentity.GetCurrent() somewhat differently. Here is the SecurityUtils.AppendWindowsAuthenticationInfo() function in .Net 4.0:  

</pre>
 

[SecurityCritical]
internal static string AppendWindowsAuthenticationInfo(string inputString, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel)
{
    if (IsDefaultNetworkCredential(credential))
    {
        string str = UnsafeGetCurrentUserSidAsString();
        return (inputString + "\0" + str + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
    }
    return (inputString + "\0" + NetworkCredentialHelper.UnsafeGetDomain(credential) + "\0" + NetworkCredentialHelper.UnsafeGetUsername(credential) + "\0" + NetworkCredentialHelper.UnsafeGetPassword(credential) + "\0" + AuthenticationLevelHelper.ToString(authenticationLevel) + "\0" + TokenImpersonationLevelHelper.ToString(impersonationLevel));
}

 

The logic for this version of SecurityUtils.AppendWindowsAuthenticationInfo() is about the same as the logic in .Net 3.5 except for the call to the new member UnsafeGetCurrentUserSidAsString() on line 11. The code to this new member function is shown below:

[SecurityCritical, SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.ControlPrincipal)]
private static string UnsafeGetCurrentUserSidAsString()
{
    using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
    {
        return identity.User.Value;
    }
}

On line 1 of the above code there is an Assert for the ControlPrincipal flag, the same flag which WindowsIdentity.GetCurrent() demands. This Assert will satisfy the stack walk caused by the Demand and no exception will be thrown. So that is how Microsoft fixed this bug in .Net 4.0. 

 

Finally! An 4.0 XBAP can call a Windows Authenticated WCF Service! Part 1

The Problem

Last year, I was on a project where I designed a solution with an XBAP and an IIS hosted WCF data service. The problem came out because the WCF service was secured with Windows integrated authentication and the XBAP, being a partial trust application, did not have the appropriate privileges needed to call the necessary CLR methods needed to package and issue a call to the secured WCF service. 

The first hint of the problem came out when I got a security exception from calling the WCF service from inside the XBAP. The security exception was because the WCF service proxy was trying to set the HttpWebRequest.UnsafeAuthenticatedConnectionSharing property. However, unrestricted web permission is required to set that property and the XBAP, which was running with the Intranet zone privleleges, did not have that permission. Here is the stack trace of the exception:

I opened a ticket with Microsoft concerning this problem and after a few days, received hotfix 959546 which was supposed to address this problem:

This hotfix did address the problem and allowed the code to proceed past the point of setting the HttpWebRequest.UnsafeAuthenticatedConnectionSharing property but a new exception was thrown higher in the callstack:

It took quite a bit of time after I first opened the ticket, worked throught he hotfix, and before Microsoft was able to clearly address this issue and by this time in my ticket lifecycle, I was working with an escalation engineer. After reporting the above problem to him, I received the below email email in which he indicating that this bug was to be fixed in .Net 4.0:

So at this point, I’m left with a problem: I can’t use the WCF service proxy to call a secured WCF service so what should I do? Should I re-implement the WCF data service as a 2.0 web service? ~~SHIVER!!~~. Grasping at straws, I decided to try using a 2.0 web service proxy to call my secured WCF data service and see what would happen. What happened was that it worked! So I released this project as an XBAP, a secure WCF data service, and a 2.0 web service proxy inside the XBAP to call the secure data service.

And that brings us to today. As you can see from the dates in the email chain above, this all happened last summer and today the .Net framework release 4.0 is in RC mode so I decided to see if this problem was fixed in 4.0. To try out this fix, I setup two quick hyper-v VMs:

  1. Win7 + Visual Studio 2010 RC
  2. 2008 R2 + IIS 7.5

To model the environment of last year’s project, I wrote a quick WCF service and an XBAP to call the service. I ran the XBAP and sure enough, everything worked just fine in 4.0, the XBAP was able to call the WCF service which was being hosted on IIS 7.5 using Windows Integrated Authentication. Just for kicks, I regressed the build’s target framework back to .Net 3.5 and ran it again just to see what would happen. What happened was I got the same exception I did last summer, so that proved to me there were no shenanigans going and that nogthing had crept in to my problem domain (you never know after a year long wait) as the outcome of my proofs were what I expected.

After going through this test with 4.0, I became curious about how exactly Microsoft fixed this problem. To satisfy this curiosity, I’ve decided to write a second part of this post and take a look at the version 3.5 CLR code using Reflector to see what exactly the code looks like that was causing this problem. And then I’ll take look at the 4.0 CLR code to see how the problem was fixed.