Share via


WCF: Discover In WCF World

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.

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="schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Information">

<TraceIdentifier>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="schemas.microsoft.com/2006/08/ServiceModel/ServiceIdentityDeterminationTraceRecord">

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

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

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

</Identity>

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

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

<Identity xmlns="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: 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="schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Information">

<TraceIdentifier>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="schemas.microsoft.com/2006/08/ServiceModel/ServiceIdentityDeterminationTraceRecord">

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

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

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

</Identity>

<EndpointReference xmlns="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

<thekspace.com/home/component/content/article/54-kerberos-and-spnego.html>

 

Important link:

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...

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

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

<www.ietf.org/rfc/rfc4178.txt>

 

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