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& message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message& message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message& 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.
Comments
Anonymous
August 09, 2010
Is there a way to specify the order where the BST will appear in the header?Anonymous
October 18, 2010
Is any way to live without BST at all ? Incoming Message signed and pass (looks like Java client) <KeyInfo> <o:SecurityTokenReference> <X509Data> <X509IssuerSerial> <X509IssuerName>CN=XXXX</X509IssuerName> <X509SerialNumber>1111111111111111111111111</X509SerialNumber> </X509IssuerSerial> </X509Data> </o:SecurityTokenReference> </KeyInfo>