What is service identity in WCF WSDL ?

For normal basicHttpBinding we use one directional authentication where only service authenticates the client. For dual authentication, we allow client to authenticate the service as well.

 

With WS Standard we use the Mutual authentication scheme and publish the service identity to the clients.

The service identity becomes part of WSDL and is available to clients, which gets used by client to authenticate to service. Client compares the endpoint identity value (local) with the actual value the endpoint authentication process returned. If they match, the client is assured it has contacted the expected service endpoint. This protects client to talk to phishing service.

When the client initiates a secure channel to send a message to a service over it, the Windows Communication Foundation (WCF) infrastructure authenticates the service, and only sends the message if the service identity matches the identity specified in the endpoint address the client uses.

 

Identity processing consists of the following stages:

• At design time, the client developer determines the service's identity from the endpoint's metadata (exposed through WSDL).

• At runtime, the client application checks the claims of the service's security credentials before sending any messages to the service.

Client does not send messages to the service until the service's credentials have been authenticated based on what is known in advance from the service's metadata.

 

UPN IDENTITY:

This ensures that the service is running under a specific Windows user account.

The user account can be either the current logged-on user or the service running under a particular user account.

This setting takes advantage of Windows Kerberos security if the service is running under a domain account within an Active Directory environment.

 

SPN IDENTITY:

This ensures that the SPN and the specific Windows account associated with the SPN identify the service.

You can use the Setspn.exe tool to associate a machine account for the service's user account.

This setting takes advantage of Windows Kerberos security if the service is running under one of the system accounts or under a domain account that has an associated SPN name with it and the computer is a member of a domain within an Active Directory environment.

 

If the service authenticates using message- or transport-level SSL with a Windows credential for authentication, and negotiates the credential, the following identity values are valid:

• DNS. The negotiation passes the service's SPN so that the DNS name can be checked. The SPN is in the form host/<dns name>.

• SPN. An explicit service SPN is returned, for example, host/myservice.

• UPN. The UPN of the service account. The UPN is in the form username@domain. For example, when the service is running in a user account, it may be username@contoso.com.

 If no identity is specified, and the client credential type is Windows, the default is SPN with the value set to the hostname part of the service endpoint address prefixed with the "host/" literal.

 

For Windows Authentication

SPN or UPN are published in WSDL because ....

Kerberos authentication requires that a UPN or SPN be supplied to the client to authenticate the service.

From IIS 7 onwards, we introduced a new concept called – KERNEL MODE SECURITY.

This is intended to simplify the SPN management and move authentication to Kernel layer.

With this security mode, the SPN is registered under the Machine Account.

If our web application pool is hosted in a custom domain account, the SPN must be registered for that user account, rather than the machine account.

http://technet.microsoft.com/en-us/library/dd632778.aspx

 

Stack used for checking the identity at client side:

at System.ServiceModel.Security.IdentityVerifier.DefaultIdentityVerifier.TryGetIdentity(EndpointAddress reference, EndpointIdentity& identity)

at System.ServiceModel.Channels.WindowsStreamSecurityUpgradeProvider.WindowsStreamSecurityUpgradeInitiator.InitiateUpgradePrepare(Stream stream, NegotiateStream& negotiateStream, String& targetName, EndpointIdentity& identity)

 

Source code method:

======================

public override bool TryGetIdentity(EndpointAddress reference, out EndpointIdentity identity)

{

    if (reference == null)

    {

        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reference");

    }

    identity = reference.Identity;

    if (identity == null)

    {

        identity = this.TryCreateDnsIdentity(reference);

    }

    if (identity == null)

    {

        SecurityTraceRecordHelper.TraceIdentityDeterminationFailure(reference, typeof(IdentityVerifier.DefaultIdentityVerifier));

        return false;

    }

    SecurityTraceRecordHelper.TraceIdentityDeterminationSuccess(reference, identity, typeof(IdentityVerifier.DefaultIdentityVerifier));

    return true;

}

 

 

SCENERIO 1:

=====================

Client passing the correct identity as UPN ...

Stack:

at System.ServiceModel.Security.IdentityVerifier.DefaultIdentityVerifier.TryGetIdentity(EndpointAddress reference, EndpointIdentity& identity)

 

EndPointAddress object is created based on the identity set in the config file.

We return success from the highlighted code…

 

public override bool TryGetIdentity(EndpointAddress reference, out EndpointIdentity identity)

{

    if (reference == null)

    {

        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reference");

    }

    identity = reference.Identity;

    if (identity == null)

    {

        identity = this.TryCreateDnsIdentity(reference);

    }

    if (identity == null)

    {

        SecurityTraceRecordHelper.TraceIdentityDeterminationFailure(reference, typeof(IdentityVerifier.DefaultIdentityVerifier));

        return false;

    }

    SecurityTraceRecordHelper.TraceIdentityDeterminationSuccess(reference, identity, typeof(IdentityVerifier.DefaultIdentityVerifier));

    return true;

}

 

 <DataItem>

<TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Information">

<TraceIdentifier>http://msdn.microsoft.com/en-US/library/System.ServiceModel.Security.SecurityIdentityDeterminationSuccess.aspx</TraceIdentifier>

<Description>Identity was determined for an EndpointReference.</Description>

<AppDomain>ConsoleApplication1.vshost.exe</AppDomain>

<ExtendedData xmlns="http://schemas.microsoft.com/2006/08/ServiceModel/ServiceIdentityDeterminationTraceRecord">

<IdentityVerifierType>System.ServiceModel.Security.IdentityVerifier+DefaultIdentityVerifier</IdentityVerifierType>

<Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">

<Upn>saurabs@fareast.corp.microsoft.com</Upn>

</Identity>

<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">

<Address>net.tcp://saurabh21.fareast.corp.microsoft.com/TCP/Service1.svc</Address>

<Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">

<Upn>saurabs@fareast.corp.microsoft.com</Upn>

</Identity>

</EndpointReference>

</ExtendedData>

</TraceRecord>

</DataItem>

 

Next we try to get the Associated SPN for the identity …

 

        internal static string GetSpnFromIdentity(EndpointIdentity identity, EndpointAddress target)

        {

            bool foundSpn = false;

            string spn = null;

            if (identity != null)

            {

                if (ClaimTypes.Spn.Equals(identity.IdentityClaim.ClaimType))

                {

                    spn = (string)identity.IdentityClaim.Resource;

                    foundSpn = true;

                }

                else if (ClaimTypes.Upn.Equals(identity.IdentityClaim.ClaimType))

                {

                    spn = (string)identity.IdentityClaim.Resource;

                    foundSpn = true;

                }

                else if (ClaimTypes.Dns.Equals(identity.IdentityClaim.ClaimType))

                {

                    spn = String.Format(CultureInfo.InvariantCulture, "host/{0}", (string)identity.IdentityClaim.Resource);

                    foundSpn = true;

                }

            }

            if (!foundSpn)

            {

                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.CannotDetermineSPNBasedOnAddress, target)));

            }

            return spn;

        }

  

Next we see..

at System.ServiceModel.Diagnostics.SecurityTraceRecordHelper.TraceIdentityVerificationSuccess(EventTraceActivity eventTraceActivity, EndpointIdentity identity, Claim claim, Type identityVerifier)

at System.ServiceModel.Security.IdentityVerifier.DefaultIdentityVerifier.CheckAccess(EndpointIdentity identity, AuthorizationContext authContext)

 

We are calling the CheckAccess method to confirm the claims received as a part of service identity...

Confirm if the claims are for same identity what we specified..

 

public override bool CheckAccess(EndpointIdentity identity, AuthorizationContext authContext)

{

    if (identity == null)

    {

        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("identity");

    }

    if (authContext == null)

    {

        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("authContext");

    }

    for (int i = 0; i < authContext.ClaimSets.Count; i++)

    {

        ClaimSet claimSet = authContext.ClaimSets[i];

        if (claimSet.ContainsClaim(identity.IdentityClaim))

        {

            SecurityTraceRecordHelper.TraceIdentityVerificationSuccess(identity, identity.IdentityClaim, base.GetType());

            return true;

        }

        string expectedSpn = null;

        if (ClaimTypes.Dns.Equals(identity.IdentityClaim.ClaimType))

        {

            expectedSpn = string.Format(CultureInfo.InvariantCulture, "host/{0}", new object[] { (string) identity.IdentityClaim.Resource });

            Claim claim = this.CheckDnsEquivalence(claimSet, expectedSpn);

            if (claim != null)

            {

                SecurityTraceRecordHelper.TraceIdentityVerificationSuccess(identity, claim, base.GetType());

                return true;

            }

        }

        SecurityIdentifier identitySid = null;

        if (ClaimTypes.Sid.Equals(identity.IdentityClaim.ClaimType))

        {

            identitySid = this.GetSecurityIdentifier(identity.IdentityClaim);

        }

        else if (ClaimTypes.Upn.Equals(identity.IdentityClaim.ClaimType))

        {

            identitySid = ((UpnEndpointIdentity) identity).GetUpnSid();

        }

        else if (ClaimTypes.Spn.Equals(identity.IdentityClaim.ClaimType))

        {

            identitySid = ((SpnEndpointIdentity) identity).GetSpnSid();

        }

        else if (ClaimTypes.Dns.Equals(identity.IdentityClaim.ClaimType))

        {

            identitySid = new SpnEndpointIdentity(expectedSpn).GetSpnSid();

        }

        if (identitySid != null)

        {

            Claim claim2 = this.CheckSidEquivalence(identitySid, claimSet);

            if (claim2 != null)

            {

                SecurityTraceRecordHelper.TraceIdentityVerificationSuccess(identity, claim2, base.GetType());

                return true;

            }

        }

    }

    SecurityTraceRecordHelper.TraceIdentityVerificationFailure(identity, authContext, base.GetType());

    return false;

}

 

 

Dumping objects:

 

Our identity Object:

0:000> !do 0x27d97ec

Name:        System.ServiceModel.UpnEndpointIdentity

MethodTable: 51800748

EEClass:     51444ad8

Size:        32(0x20) bytes

File:        C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.ServiceModel\v4.0_4.0.0.0__b77a5c561934e089\System.ServiceModel.dll

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

4ffcb198  4002575        4 ...odel.Claims.Claim  0 instance 027d9930 identityClaim

4ffcb2b8  4002576        8 ...m.IdentityModel]]  0 instance 00000000 claimComparer

65dc15b8  400257d        c ...ecurityIdentifier  0 instance 00000000 upnSid

65dc6f18  400257e       18       System.Boolean  1 instance        0 hasUpnSidBeenComputed

65dc29b4  400257f       10 ...l.WindowsIdentity  0 instance 00000000 windowsIdentity

65dcb060  4002580       14        System.Object  0 instance 027d980c thisLock

 

 

0:000> !DumpObj /d 027d9930

Name:        System.IdentityModel.Claims.Claim

MethodTable: 4ffcb198

EEClass:     4ff64b38

Size:        24(0x18) bytes

File:        C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.IdentityModel\v4.0_4.0.0.0__b77a5c561934e089\System.IdentityModel.dll

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

65dcacc0  40007a9        4        System.String  0 instance 027d98b0 claimType

65dcb060  40007aa        8        System.Object  0 instance 02732d6c resource

65dcacc0  40007ab        c        System.String  0 instance 027d9818 right

4ffcb2b8  40007ac       10 ...m.IdentityModel]]  0 instance 027d9954 comparer

4ffcb198  40007a8       dc ...odel.Claims.Claim  0   static 0293d9b8 system

 

 

0:000> !DumpObj /d 027d98b0

Name:        System.String

MethodTable: 65dcacc0

EEClass:     659d486c

Size:        128(0x80) bytes

File:        C:\windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

String:      http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

65dcc480  40000aa        4         System.Int32  1 instance       57 m_stringLength

65dcb6b8  40000ab        8          System.Char  1 instance       68 m_firstChar

65dcacc0  40000ac        c        System.String  0   shared   static Empty

    >> Domain:Value  008867e0:NotInit  <<

 

 

0:000> !DumpObj /d 02732d6c

Name:        System.String

MethodTable: 65dcacc0

EEClass:     659d486c

Size:        82(0x52) bytes

File:        C:\windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

String:      saurabs@fareast.corp.microsoft.com

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

65dcc480  40000aa        4         System.Int32  1 instance       34 m_stringLength

65dcb6b8  40000ab        8          System.Char  1 instance       73 m_firstChar

65dcacc0  40000ac        c        System.String  0   shared   static Empty

 

 

 

Authorization context received from the service…

0:000> !do 0x293db44

Name:        System.IdentityModel.SecurityUtils+SimpleAuthorizationContext

MethodTable: 4ffcbcb0

EEClass:     4ff6c610

Size:        20(0x14) bytes

File:        C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.IdentityModel\v4.0_4.0.0.0__b77a5c561934e089\System.IdentityModel.dll

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

4ffcb998  40011db        4 ....SecurityUniqueId  0 instance 00000000 id

4ffcbb90  40011dc        8 ...conditionalPolicy  0 instance 0293d8bc policy

659d86a4  40011dd        c ...bject, mscorlib]]  0 instance 0293db7c properties

 

 

0:000> !DumpObj /d 0293d8bc

Name:        System.IdentityModel.Policy.UnconditionalPolicy

MethodTable: 4ffcbb90

EEClass:     4ff6c574

Size:        40(0x28) bytes

File:        C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.IdentityModel\v4.0_4.0.0.0__b77a5c561934e089\System.IdentityModel.dll

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

4ffcb998  400088d        4 ....SecurityUniqueId  0 instance 00000000 id

4ffcbbec  400088e        8 ...l.Claims.ClaimSet  0 instance 0293d9e8 issuer

4ffcbbec  400088f        c ...l.Claims.ClaimSet  0 instance 0293d88c issuance

4ff5179c  4000890       10 ...m.IdentityModel]]  0 instance 00000000 issuances

65dc8bdc  4000891       1c      System.DateTime  1 instance 0293d8d8 expirationTime

65dc3968  4000892       14 ...incipal.IIdentity  0 instance 0293d7b8 primaryIdentity

65dc6f18  4000893       18       System.Boolean  1 instance        0 disposable

65dc6f18  4000894       19       System.Boolean  1 instance        0 disposed

 

 

0:000> !DumpObj /d 0293d7b8

Name:        System.Security.Principal.GenericIdentity

MethodTable: 65dc2d48

EEClass:     65adcafc

Size:        64(0x40) bytes

File:        C:\windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

659e0bf0  4001b15        4 ...Claim, mscorlib]]  0 instance 0293d7f8 m_instanceClaims

659e0c90  4001b16        8 ...lib]], mscorlib]]  0 instance 0293d810 m_externalClaims

65dcacc0  4001b17        c        System.String  0 instance 0293d43c m_nameType

65dcacc0  4001b18       10        System.String  0 instance 0293d548 m_roleType

65dcacc0  4001b19       14        System.String  0 instance 0293d428 m_version

65dc652c  4001b1a       18 ...ms.ClaimsIdentity  0 instance 00000000 m_actor

65dcacc0  4001b1b       1c        System.String  0 instance 00000000 m_authenticationType

65dcb060  4001b1c       20        System.Object  0 instance 00000000 m_bootstrapContext

65dcacc0  4001b1d       24        System.String  0 instance 00000000 m_label

65dcacc0  4001b1e       28        System.String  0 instance 00000000 m_serializedNameType

65dcacc0  4001b1f       2c        System.String  0 instance 00000000 m_serializedRoleType

65dcacc0  4001b20       30        System.String  0 instance 00000000 m_serializedClaims

65dcacc0  4001b2a       34        System.String  0 instance 02732d6c m_name

65dcacc0  4001b2b       38        System.String  0 instance 02621228 m_type

 

 

0:000> !DumpObj /d 02732d6c

Name:        System.String

MethodTable: 65dcacc0

EEClass:     659d486c

Size:        82(0x52) bytes

File:        C:\windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

String:      saurabs@fareast.corp.microsoft.com

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

65dcc480  40000aa        4         System.Int32  1 instance       34 m_stringLength

65dcb6b8  40000ab        8          System.Char  1 instance       73 m_firstChar

65dcacc0  40000ac        c        System.String  0   shared   static Empty

    >> Domain:Value  008867e0:NotInit  <<

 

 

We can see the identity in both is same..

And eventually the identity verification succeeds...

 

Note:

The method is only called if we are on Kerberos authentication scheme..

Otherwise we fall back to NTLM..

Next case demonstrate the same…

 

 Scenario 2:

================

When client don't specify any identity ......

However he is supposed to specify as UPN.....

 

Stack:

at System.ServiceModel.Diagnostics.SecurityTraceRecordHelper.TraceIdentityDeterminationSuccess(EndpointAddress epr, EndpointIdentity identity, Type identityVerifier)

at System.ServiceModel.Security.IdentityVerifier.DefaultIdentityVerifier.TryGetIdentity(EndpointAddress reference, EndpointIdentity& identity)

 

<TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Information">

<TraceIdentifier>http://msdn.microsoft.com/en-US/library/System.ServiceModel.Security.SecurityIdentityDeterminationSuccess.aspx</TraceIdentifier>

<Description>Identity was determined for an EndpointReference.</Description>

<AppDomain>ConsoleApplication1.vshost.exe</AppDomain>

<ExtendedData xmlns="http://schemas.microsoft.com/2006/08/ServiceModel/ServiceIdentityDeterminationTraceRecord">

<IdentityVerifierType>System.ServiceModel.Security.IdentityVerifier+DefaultIdentityVerifier</IdentityVerifierType>

<Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">

<Dns>saurabh21.fareast.corp.microsoft.com</Dns>

</Identity>

<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">

<Address>net.tcp://saurabh21.fareast.corp.microsoft.com/TCP/Service1.svc</Address>

</EndpointReference>

</ExtendedData>

</TraceRecord>

 

We can see here we end up in setting the DNS identity....

Because of this code....

 

identity = reference.Identity;

    if (identity == null)

    {

        identity = this.TryCreateDnsIdentity(reference);

    }

 

private EndpointIdentity TryCreateDnsIdentity(EndpointAddress reference)

{

    Uri uri = reference.Uri;

    if (!uri.IsAbsoluteUri)

    {

        return null;

    }

    return EndpointIdentity.CreateDnsIdentity(uri.DnsSafeHost);

}

 

 

So it reads the service URL and get the DNSSafeHost value from there...

In my case...

URI is: net.tcp://saurabh21.fareast.corp.microsoft.com/TCP/Service1.svc

so "DnsSafeHost" is "saurabh21.fareast.corp.microsoft.com"

 

Since my service also happen to run on same box so we are good to go in this case...

 

BUT … this is good to go only if we ok with NTLM …

Since we did not specified the correct UPN, we have fallen back to NTLM

 

If I set an end point behavior at client side, where I set allowNTLM = false..

Mutual authentication check will fail…

 

Then the Case 2 will fail…. As expected….

Error:

The remote server did not satisfy the mutual authentication requirement.

 

Source Code…

            void ValidateMutualAuth(EndpointIdentity expectedIdentity, NegotiateStream negotiateStream,

                SecurityMessageProperty remoteSecurity, bool allowNtlm)

            {

                if (negotiateStream.IsMutuallyAuthenticated)

                {

                    if (expectedIdentity != null)

                    {

                        if (!parent.IdentityVerifier.CheckAccess(expectedIdentity,

                            remoteSecurity.ServiceSecurityContext.AuthorizationContext))

                        {

                            string primaryIdentity = SecurityUtils.GetIdentityNamesFromContext(remoteSecurity.ServiceSecurityContext.AuthorizationContext);

                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(

                                SR.RemoteIdentityFailedVerification, primaryIdentity)));

                        }

                    }

                }

                else if (!allowNtlm)

                {

                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(

                        SR.StreamMutualAuthNotSatisfied)));

                }

            }

  

EVEN NTLM check may fail… if the host header and the DNS name are different…

if I host my service runs on a different box name... not on saurabh21.fareast.corp.microsoft.com

Then the check will eventually fail... and this will eventually help us detect the phishing sites…

 

Let’s see how...

I configured my WCF service to run on XYZ.com as host header.....

So the end point to client now becomes....

net.tcp://xyz.com/TCP/Service1.svc

Eventually the DNS value computed by client  (in case we don't add any UPN) will be XYZ.com

 

In this case, when the client receives the Windows (Kerberos) credentials /claims  for the service, it expects to see the DNS value to be XYZ.com.

and we know this is invalid.. so finally server reject the negotiation with this error...

 

 

SCENERIO 3

==============

If we pass blank or wrong UPN ... why it works ?

It works because we fall back to NTLM...

When falling back to NTLM, no matter what we specify it works.... because we are no longer on the Kerberos layer.

 

To stop this behavior..

we can set the allowNTLM = false in the client end point behavior...

Once done we no longer be able to get the blank or wrong UPN working...

       <behaviors>

        <endpointBehaviors>

          <behavior name="my">

            <clientCredentials>

              <windows allowNtlm="false"/>

            </clientCredentials>

          </behavior>

        </endpointBehaviors>

      </behaviors>

 

        <client>

            <endpoint address="net.tcp://xyz.com/TCP/Service1.svc" binding="netTcpBinding" behaviorConfiguration="my"

                bindingConfiguration="net" contract="ServiceReference1.IService1"

                name="net">

                <identity>

                    <userPrincipalName value="" />

                </identity>

            </endpoint>

        </client>

 

 

SPNEGO and KERBEROS

http://thekspace.com/home/component/content/article/54-kerberos-and-spnego.html

 

Important link:

http://msdn.microsoft.com/en-us/library/bb628618.aspx

 

Net.Tcp internally relies on the SPNEGO protocol ... and we know for sure that falling back to NTLM will work

Until we explicitly set it to false in end point behavior...

==================================================

http://msdn.microsoft.com/en-us/library/dd357379.aspx

http://www.ietf.org/rfc/rfc4178.txt

 

Hope the content help in understanding the <identity> in WCF World.