WCF Security Interoperability Guidelines – 2 : Reference Style of a Primary Signing Token inside a request

In my last post, I had discussed the supported ways to reference a primary signing token inside a response. We will focus on request this time round. Consider a scenario where we have a non-.NET client consuming a WCF service, security requirements of the underlying communication remaining the same as my last post:

                - Security is implemented at the message layer with either parties communicating over HTTP.

                 - Both client and service mutually authenticates via X509 certificates.

                 - Additional client authentication in terms of a supporting username token.

Sometimes we might choose to pass only a pointer to X509Certificate (may be its issuer name and serial number) instead of the whole certificate encapsulated within a binary security token (BST). In such a case, incoming security header to a WCF service will look as follows:

     <o:Security s:mustUnderstand="1" xmlns:o="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">

          <u:Timestamp u:Id="uuid-31bf49bd-df18-41d4-9c9d-559e56a99045-1">

               <u:Created>2010-05-24T21:48:37.010Z</u:Created>

               <u:Expires>2010-05-24T21:53:37.010Z</u:Expires>

          </u:Timestamp>

          <o:UsernameToken u:Id="uuid-86024b35-af0f-40f9-8ba7-9e73381a9599-1">

               <o:Username>

                        <!-- Removed-->

               </o:Username>

               <o:Password>

                       <!-- Removed-->

               </o:Password>

          </o:UsernameToken>

          <Signature xmlns="https://www.w3.org/2000/09/xmldsig#">

                    <SignedInfo>

                    .

                    .

                    <SignatureValue>GKClvYiiqAIbcjSnWXJoxuoaziY5Yg…………………</SignatureValue>

                     < KeyInfo >

                          < o:SecurityTokenReference >

                               <X509Data >

                                   < X509IssuerSerial >

                                        < X509IssuerName > CN=XXXX </ X509IssuerName >

                                        < X509SerialNumber > 665497663279805416273 </ X509SerialNumber >

                                   </ X509IssuerSerial >

                               </ X509Data >

                          </ o:SecurityTokenReference >

                     </ KeyInfo >

          </Signature>

     </o:Security>

Such a request will fail at the service end with the following exception:

<Exception>

<ExceptionType>System.ServiceModel.Security.MessageSecurityException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

<Message>Cannot resolve KeyInfo for verifying signature: KeyInfo 'SecurityKeyIdentifier

    (

    IsReadOnly = False,

    Count = 1,

    Clause[0] = X509IssuerSerialKeyIdentifierClause(Issuer = 'CN=XXXX', Serial = '665497663279805416273')

    )

', available tokens 'SecurityTokenResolver

    (

    TokenCount = 0,

    )

'.</Message>

<StackTrace>

at System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.ResolveSignatureToken(SecurityKeyIdentifier keyIdentifier, SecurityTokenResolver resolver, Boolean isPrimarySignature)

at System.ServiceModel.Security.WSSecurityOneDotZeroReceiveSecurityHeader.VerifySignature(SignedXml signedXml, Boolean isPrimarySignature, SecurityHeaderTokenResolver resolver, Object signatureTarget, String id)

at System.ServiceModel.Security.ReceiveSecurityHeader.ProcessPrimarySignature(SignedXml signedXml, Boolean isFromDecryptedSource)

at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)

at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout)

at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message&amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

Let us first see why the request fails at the service end. To start with, we need to understand what a System.ServiceModel.Security.SecurityTokenResolver is. This is a utility class used to retrieve security tokens from a message given a key identifier or a key identifier clause. More details about this class are available here. WCF runtime, upon receipt of a request, populates this class with the tokens present inside the security header. Corresponding call stack of such a add operation is as follows:

0000000002cde4a8 000007fee4edc845 System.ServiceModel.Security.SecurityHeaderTokenResolver.Add(System.IdentityModel.Tokens.SecurityToken, System.ServiceModel.Security.Tokens.SecurityTokenReferenceStyle, System.ServiceModel.Security.Tokens.SecurityTokenParameters)

0000000002cde4b0 000007fee403b85d System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(System.Xml.XmlDictionaryReader, Int32, Byte[], System.IdentityModel.Tokens.SecurityToken, System.String, System.TimeSpan)

0000000002cde560 000007fee403afb9 System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(System.Xml.XmlDictionaryReader)

0000000002cde5e0 000007fee403ab8b System.ServiceModel.Security.ReceiveSecurityHeader.Process(System.TimeSpan)

0000000002cde680 000007fee4a41f02 System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(System.ServiceModel.Security.ReceiveSecurityHeader, System.ServiceModel.Channels.Message ByRef, System.IdentityModel.Tokens.SecurityToken, System.TimeSpan, System.ServiceModel.Security.SecurityProtocolCorrelationState[])

0000000002cde700 000007fee4039e2c System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(System.ServiceModel.Channels.Message ByRef, System.String, System.TimeSpan, System.ServiceModel.Security.SecurityProtocolCorrelationState[])

Going by the security header mentioned in the beginning, SecurityHeaderTokenResolver.Add (it is also referred to as ‘primaryTokenResolver’) method is called once for UsernameToken present in the request. A pertinent question at this point: what happens to the X509SecurityToken referenced inside <KeyInfo> element? Why are we ignoring that while populating the resolver with security tokens? Such a token is referred to as an ‘outOfBandToken’. We need to handle such a token via a custom outOfBandTokenResolver. WCF runtime does not populate primaryTokenResolver with such outOfBandTokens. Neither does it use an outOfBandTokenResolver while processing a primary signature. You can verify that from ReceiveSecurityHeader.ProcessPrimarySignature method:

private void ProcessPrimarySignature(SignedXml signedXml, bool isFromDecryptedSource)

{

       this.orderTracker.OnProcessSignature(isFromDecryptedSource);

       this.primarySignatureValue = signedXml.GetSignatureValue();

       if (this.nonceCache != null)

       {

       CheckNonce(this.nonceCache, this.primarySignatureValue);

       }

       SecurityToken token = this.VerifySignature(signedXml, true, this.primaryTokenResolver, null, null);

What we have is a supporting username token inside primary token resolver and a X509IssuerKeyIdentifierClause key identifier passed to System.ServiceModel.Security.SecurityHeaderTokenResolver.ResolveToken method. Unable to resolve the referenced token, this method returns a NULL. Eventually a MessageSecurityException is thrown from WSSecurityOneDotZeroReceiveSecurityHeader.ResolveSignatureToken:

if (token == null)

{

       throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString("UnableToResolveKeyInfoForVerifyingSignature", new object[] { keyIdentifier, resolver })));

}

So how a primary signing token should be referenced inside a request security header? ONLY way supported by WCF is passing an X509SecurityToken inside a BST. Working security header:

       <o:Security s:mustUnderstand="1" xmlns:o="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">

           <u:Timestamp u:Id="uuid-4244331a-1cd0-4c16-9385-318ef75a6f81-1">

                   <u:Created>2010-05-24T21:47:47.061Z</u:Created>

                   <u:Expires>2010-05-24T21:52:47.061Z</u:Expires>

           </u:Timestamp>

           < o:BinarySecurityToken >

                    <!-- Removed -->

           </ o:BinarySecurityToken >

           <o:UsernameToken u:Id="uuid-f79a13e9-6edd-4bae-aa88-64381cd0324d-1">

                   <o:Username>

                           <!-- Removed-->

                   </o:Username>

                   <o:Password>

                           <!-- Removed-->

                   </o:Password>

           </o:UsernameToken>

           <Signature xmlns="https://www.w3.org/2000/09/xmldsig#">

                   <SignedInfo>

                   .

                   .

                   <SignatureValue>aAP88k0ECFC3ao5S98mSA………..</SignatureValue>

                    < KeyInfo >

                          < o:SecurityTokenReference >

                                  < o:Reference URI =" #uuid-f7913e9-6edd-4bae-aa88-64381cd0324d-3 "></ o:Reference >

                          </ o:SecurityTokenReference >

                   </ KeyInfo >

            </Signature>

       </o:Security>

Next time you have a non-.NET client talking with a WCF service and you run into a similar security exception, make sure to place your primary signing token within a BST.