How do we convert a WindowsIdentity
to a NetworkCredential
? I am testing my WCF service to verify that anonymous callers are blocked. To do this I want to do something like:
myProxy.ClientCredentials.Windows.ClientCredential = foo(WindowsIdentity.GetAnonymous());
where foo
is a method that converts a WindowsIdentity
to a NetworkCredential
Generally speaking foo does not exist. Since a NetworkCredential is a wider object that WindowsIdentity. That is to say I can use NetworkCredential where I need a WindowsIdentity but not the other way around.
The reason is due to security.
NetworkCredential means you are allowed to take this Identity and use its associated CREDENTIALS on ANOTHER machine.
This means that
I will assume that the credentials came from another machine (due to WCF). Again for security reasons, that Network Credential has been converted to a LocalCredential when crossing onto this machine (unless we are talking about Delegation rather than Impersonation). The client machine GAVE the right to use its credentials on the server machine and only the server machine.
If you want to get the NetworkCredential back out you need to do something called Delegation, because of what is called the Multi-Hop problem. This involves Kerberos, a three headed evil dog of the underworld. You don't want to deal with Kerberos.
Generally speaking, by default WCF proxies do not send credentials with their calls. You generally need to set the ClientCredentials or set
proxy.UseDefaultCredentials = true
Not providing either normally means no credentials and hence Anonymous auth.
Answering my own question:
It is not possible to convert a WindowsIdentity
to a NetworkCredential
. To test if anonymous callers are blocked, impersonate current thread with a null session token, and then make call to WCF service. Note: do not use WindowsIdentity.GetAnonymous. This method is useless (guess it was incorrectly implemented, and never been corrected). Code to impersonate current thread with null session token (no error handling is done):
public static class Helper
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetCurrentThread();
[DllImport("advapi32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern bool ImpersonateAnonymousToken(IntPtr handle);
public static void ImpersonateAnonymousUser()
{
ImpersonateAnonymousToken(GetCurrentThread());
}
}
static string ToString(IIdentity identity)
{
return string.Format("{0} {1} {2}", identity.Name, identity.IsAuthenticated, identity.AuthenticationType);
}
static void Main(string[] args)
{
Console.WriteLine(ToString(WindowsIdentity.GetCurrent()));
Helper.ImpersonateAnonymousUser();
Console.WriteLine(ToString(WindowsIdentity.GetCurrent()));
}
Output:
my machine\me True NTLM
NT AUTHORITY\ANONYMOUS LOGON False
In response to Edmund's comment, setting proxy.ClientCredentials.Windows.ClientCredential
to null
will not do what is intended - make request as an anonymous user. Here is my complete test code and its output:
Service Code:
public class Service1 : IService1
{
// note that if client is not authenticated, this code will never get a chance to execute
// exception will happen before that
// therefore there is no need to decorate this method with a
// [PrincipalPermission(SecurityAction.Demand, Authenticated=true)] attribute
public string GetData()
{
try
{
var identity = Thread.CurrentPrincipal.Identity;
return string.Concat(identity.Name, ",", identity.IsAuthenticated, ",", identity.AuthenticationType);
}
catch (Exception e)
{
return string.Concat(e.Message, "\\r\\n", e.StackTrace);
}
}
}
Service config:
<services>
<service name="WcfService1.Service1">
<host>
<baseAddresses>
<add baseAddress="http://mymachine/Service1/" />
</baseAddresses>
</host>
<endpoint address="Service1"
binding ="customBinding"
bindingConfiguration="myHttpBinding"
contract="WcfService1.IService1">
</endpoint>
</service>
</services>
<bindings>
<customBinding>
<binding name="myHttpBinding">
<reliableSession/>
<binaryMessageEncoding />
<httpTransport maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647"
authenticationScheme="IntegratedWindowsAuthentication" />
</binding>
</customBinding>
</bindings>
Client code:
static void MakeRequest()
{
try
{
using (var svc = new Service1Client())
{
Console.WriteLine(svc.GetData());
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
static void Test3()
{
Console.WriteLine("using {0}", ToString(WindowsIdentity.GetCurrent()));
MakeRequest();
Console.WriteLine();
Console.WriteLine("setting svc.ClientCredentials.Windows.ClientCredential to null...");
try
{
using (var svc = new Service1Client())
{
svc.ClientCredentials.Windows.ClientCredential = null;
Console.WriteLine(svc.GetData());
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
Console.WriteLine();
ImpersonateAnonymousUser();
Console.WriteLine("using {0}", ToString(WindowsIdentity.GetCurrent()));
MakeRequest();
Console.WriteLine();
}
Client config:
<bindings>
<customBinding>
<binding name="CustomBinding_IService1">
<reliableSession />
<binaryMessageEncoding />
<httpTransport authenticationScheme="Negotiate" />
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="mymachine/Service1/Service1.svc/Service1"
binding="customBinding" bindingConfiguration="CustomBinding_IService1"
contract="ServiceReference1.IService1" name="CustomBinding_IService1">
<identity>
<servicePrincipalName value="host/mymachine" />
</identity>
</endpoint>
</client>
<behaviors>
<endpointBehaviors>
<!-- this setting protects the client by prohibiting the service to assume identity of client
via imperonation and/or delegation and then doing bad things -->
<behavior name="ImpersonationBehavior">
<clientCredentials>
<windows allowedImpersonationLevel="Identification"/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
Output:
using mymachine\me True Negotiate
mymachine\me,True,Negotiate
setting svc.ClientCredentials.Windows.ClientCredential to null...
mymachine\me,True,Negotiate
using NT AUTHORITY\ANONYMOUS LOGON False
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be
used for communication because it is in the Faulted state.
Server stack trace:
at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage req
Msg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgDa
ta, Int32 type)
at System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
at System.ServiceModel.ClientBase`1.System.ServiceModel.ICommunicationObject.
Close(TimeSpan timeout)
at System.ServiceModel.ClientBase`1.Close()
at System.ServiceModel.ClientBase`1.System.IDisposable.Dispose()
at TestClient.Program.MakeRequest()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With