Routing Secured SOAP Messages Through Multiple SOAP Intermediaries Using WSE 2.0
William Tay
NCS Pte. Ltd
March 2005
Applies to:
Web Services Enhancements 2.0 for Microsoft .NET
SOAP messaging
Summary: Route secure SOAP messages through multiple HTTP SOAP intermediaries using "Next-Hop" mechanisms, and use the ExtendedSecurity function to form a secured chain of SOAP nodes through which messages must pass. (41 printed pages)
Download the associated sample code, SecureRoutingWSE20.msi.
Contents
Introduction
WSE 2.0 and Next-Hop Routing
Multiple SOAP Intermediaries Routing Example
X.509 Digital Certificate Public Key Encryption
The SOAP Router and the ExtendedSecurity Function
Signature Checking and Verification
Security Header Extensions
Conclusion
Introduction
XML Web services are increasingly being adopted in the enterprise environment, and Web Services Enhancements (WSE) for Microsoft .NET provides many useful features that are needed and desired by enterprises. While some of the WS-* specifications are still evolving, important core specifications, such as WS-Security, have been submitted to the Technical Committee of OASIS, and WS-Addressing specifications have been submitted to the Working Group of W3C.
Note At the time of this writing, the current version is WSE 2.0 SP2.
The implementation of XML Web services has also brought closer to life the benefits and goals of the architectural philosophy of service-orientation. It is important to note that the objective of designing good business software is to deliver agile systems, not service-oriented architectures. Rather, look at service-orientation as an approach by which business and technological agility can be achieved. XML Web services offer a good implementation mechanism to achieve service-orientation. However, bear in mind that it takes more than adopting XML Web services protocols in systems deployment to achieve the desired agility. Basic, good design principles need to be adhered to, as well. While I won't go into discussion of what constitutes services and the design principles of service-orientation, it is important to think of services as some functional block that offers certain application, business, or other features. Services can be, amongst other things:
- Message destinations in terms of applications
- Message handlers in terms of infrastructure
- Message carriers in terms of transport
In this article, I will explain how to route secure SOAP messages through multiple HTTP SOAP intermediaries, each node providing a specific service before the message reaches the final endpoint. Each of the SOAP intermediary nodes performs a critical role in providing a functional service before routing to the next logical node in the entire application system.
WSE 2.0 and Next-Hop Routing
WSE 2.0 supports the WS-Addressing specifications, which support the concept of the "Next-Hop" routing mechanisms. The basis of this concept is that SOAP message senders should only know of a single endpoint. This next logical endpoint takes care of and abstracts away the additional routing needs. This "Next-Hop" routing is essentially how TCP/IP works. A message traveling on a TCP/IP transport contains information about where a message came from and where it's going, but it doesn't contain any details on how the message gets to where it is. As the message travels through the TCP/IP network, each node inspects the header to see where it's headed and then it sends the message to the next node nearest to the destination. This continues until the destination is reached. This way of dispatching messages through multiple SOAP intermediaries explains some of the design reasoning behind WS-Addressing. It also offers the reality of a secure "Next-Hop" routing mechanism, which cannot be easily implemented with WS-Routing. Incidentally, WS-Routing, which is implemented in WSE 1.0, is superseded by WS-Addressing in WSE 2.0.
As services compose at coarse-grained message boundaries, this form of message routing becomes the primary extensibility model and it can be very powerful with the implementation of standard XML Web services protocols in an entire grid of message pipelines or message bus.
This article demonstrates some of the many cool features that can be developed and implemented easily with WSE 2.0. More enterprises are adopting the use of Web services, and with the maturing of WS-Security, we are seeing the adoption and use of Web services beyond the DMZ of the corporate organizational boundaries. I am seeing a rise in routing requirements of SOAP messages that can solve many potential business problems. Examples of problems that can be solved are in scenarios where:
- The service consumer (SC) and the service provider (SP) have no trust for each other and thus need trusted intermediaries to route messages across.
- In some cases, the SP may reside on a different sub-domain from the SC and will therefore need intermediaries to route messages across.
- In good service-orientation principles, the SP should be built with no integration of the SC in mind, and would therefore need other trusted intermediaries to relay messages to complete certain business processes and functions.
- There is a need to perform authentication or other infrastructure services that are not specific to the business service itself. Sometimes, different parties own these services. Designing and implementing a routing pattern such as the one explained above promotes good practice in the separation of concerns. This aids tremendously in extensibility and scalability of the entire system application.
- There is a need to abstract the location endpoint of the ultimate receiver from the consumer.
Multiple SOAP Intermediaries Routing Example
Let us imagine and assume a scenario where routing of messages through multiple SOAP intermediaries may be required.

Figure 1. Message-routing scenario with multiple SOAP intermediaries
Figure 1 illustrates an architectural design view of the message dispatching and routing chain of the solution. This involves a cross-organizational boundary communication call where a service consumer from ABC Company needs to invoke a secured SOAP service hosted within the internal network of XYZ Company.
There are a few obstacles along the way that needs to be solved. Obviously, having to communicate with the service provider within the company's internal firewall and network from the Internet is one challenge. This is a very probable scenario in today's world where companies usually like to start or test their Web service implementations internally first before exposing it to external partners and customers over the Internet. In our case, we can easily implement a SOAP message router within the DMZ, but on the same sub-domain that forwards the requests to the listening host. Moreover, this message router can also apply certain message assertions so that it complies with the existing service policies: for example, a policy to assert that the message needs to be signed and encrypted with a certain X509 digital certificate private key which is only available within the confines of XYZ Company.
Requiring a policy assertion to use an asymmetric key algorithm (X509) is inherently more secure than a username/password token for signing and encryption. However, it will be pretty unrealistic to expect that the hundreds of service consumers will each have their own set of X509 digital certificates key pair. In this case, I propose to have another SOAP message router within the confines of ABC Company whose role is to validate and verify the user credentials and then sign those messages with a common set of X509 digital certificates key pair. We may utilize just one or a few of these digital certificates in this instance. This is significantly cheaper, and more importantly, more manageable and maintainable than expecting all service consumers to have their own set of Public-Private key pair. By implementing these sets of requirements described above, messages being dispatched out to the Internet from ABC Company are signed and encrypted separately by different sets of X509 digital certificate keys, which, in turn, is much more secure than signing and encrypting them with just the username tokens alone.
To improve availability of the SOAP message routers, you may choose to run multiple instances of them and load-balance them on the network to better distribute and share the connection and processing loads.
Note With the solution I have proposed above, I am assuming that XYZ Company trusts the delegation of the authentication of all service consumers to the SOAP intermediary of ABC Company. In other words, XYZ Company trusts all clients who are successfully authenticated by ABC Company. I am also assuming no differentiation of privileges or roles in accessing the SOAP service of XYZ Company. However, if XYZ Company chooses to take a more proactive and assertive role in the authentication of the service consumers of ABC Company, it is a perfectly acceptable solution to sign with the username token and then encrypt all data, including the username token in the message, with an X509 digital certificate. For more information, see Securing the Username Token with WSE 2.0 by Keith Brown. The only obvious cost to this alternative is the need to maintain another set of username information at another location. This is a huge tradeoff in this instance, and was not an option in the previous example.
Now, let us go more into details and assume that the service consumer (SC) and the service provider (SP) are separated by an arbitrary number of trusted SOAP intermediaries. The SC, in this instance, is the message sender and the SP is the message receiver. The exact structure of this routing chain is unknown to both the SC and SP. Certain business architectural requirements also explicitly specify that the SP only implement the functionality of the destination service and nothing else. The authentication service of the solution is delegated to and implemented by another node in the routing chain (SOAP Router 1, or SR1). Further extensibility is introduced into the mix when another node (SOAP Router 2, or SR2) is tasked with the responsibility to sign and then dispatch the SOAP message to the appropriate destination endpoint. Therefore, in this example, the first intermediate SOAP node (SR1) provides the authentication service while the second node (SR2) provides the URI Referral service. There you have it, each intermediary node, which may be owned by different stakeholders, plays a very important role in the entire routing chain and there is no other way a message can get from the SC to the SP without passing through SR1 and SR2. In other words, the starting SOAP message from the SC gets massaged a couple of times before it reaches a state whereby the SP can understand and process it. See Table 1 and the sequence diagram in Figure 2 for a better representation.
Table 1. SOAP intermediary message-routing process
| SOAP Node | Role |
|---|---|
| SC |
|
| SR1 |
|
| SR2 |
|
| SP |
|

Figure 2. SOAP intermediary message-routing sequence
As you can see in Figure 2, the message will need to be authenticated along the way through these SOAP intermediaries. New security headers targeted for different SOAP actors can be added into the message as well. The corresponding targeted SOAP actor will know to process. Once a security header targeted for a specific node is processed successfully, it is removed from the SOAP message.
According to the WS-Security specifications of OASIS:
"The header block provides a mechanism for attaching security-related information targeted at a specific recipient in the form of a SOAP actor/role. This may be either the ultimate recipient of the message or an intermediary. Consequently, elements of this type may be present multiple times in a SOAP message. An active intermediary on the message path MAY add one or more new sub-elements to an existing header block if they are targeted for its SOAP node or it MAY add one or more new headers for additional targets..."
Building and developing message routing via intermediaries in Microsoft .NET using powerful SOAP extensions in WSE 2.0 is straightforward. WSE 2.0 is extensible, allowing customized filters to be plugged into the incoming and outgoing pipelines. Add that functionality to the packaged filters included in WSE 2.0 (such as the security, referral, trace, and policy filters) and you are on your way to building enterprise-ready XML services.
Let's look at some code, starting with the implementation of the Service Provider (SP) in Listing 1.
Listing 1
<SoapActor("http://localhost/dotnetproj/securesoapnodechain/sr1/sr1route.ashx"), _
System.Web.Services.WebService(Namespace:="http://demos.softwaremaker.net/SecureSOAPNodeChain/index")> _
Public Class index
Inherits System.Web.Services.WebService
<WebMethod()> _
Public Function ProcessTransaction(ByVal sAccID As String) As String
If sAccID.Length > 0 Then
'Do some Transaction with the Account ID
'Once that is done, return a successful message
Return "Transaction Success"
Else
Return "Transaction Failure"
End If
End Function
End Class
Besides changing the default namespace generated by Visual Studio .NET 2003 to any arbitrary string you prefer to use, another important thing to take note of is the addition of the SoapActor attribute into the code. This is an important attribute to add, as it indicates itself as the recipient of a WS-Addressing header element:
http://localhost/dotnetproj/securesoapnodechain/sr1/sr1route.ashx
This simple ProcessTransaction function validates for an input parameter, and does some processing before returning a status message back to the requester.
X.509 Digital Certificate Public Key Encryption
Next, because the message body contains sensitive context, this SP needs to make sure that the SOAP message body is encrypted with its X.509 digital certificate public key. When a message is received, only the corresponding X.509 digital certificate private key, which only the SP holds, can decrypt the message content successfully.
We can easily do that via code, first checking for encryption content, and then making sure that the X.509 Key pairs correspond to each other. However, WS-Policy, supported in WSE 2.0, makes it a lot easier by enabling a set of policy assertions that specify any message meant for the SP MUST be encrypted with its Public key via a configuration file. This makes the entire system much more maintainable as there is no need for any code changes or assembly recompilation should X.509 key pairs change due to some corporate security policy requirements. Therefore, I would recommend using WS-Policy to be the most maintainable way to build WSE applications.
I will not go into the workings of WS-Policy here, as it is beyond the scope of this article. For more information, see Understanding WS-Policy. The attached project implements a policy file at the SP. For reference, the encryption section Confidentiality element of the policy file is reproduced here in Listing 2.
Listing 2
<wsp:Policy wsu:Id=" #Sign-X.509-Encrypt-X.509">
<wssp:Confidentiality wsp:Usage="wsp:Required">
<wssp:KeyInfo>
<wssp:SecurityToken>
<wssp:TokenType>
http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-x509-token-profile-1.0#X509v3
</wssp:TokenType>
<wssp:TokenIssuer>CN=Root Agency</wssp:TokenIssuer>
<wssp:Claims>
<wssp:SubjectName MatchType="wssp:Exact">
CN=WSE2QuickStartServer
</wssp:SubjectName>
<wssp:X509Extension OID="2.5.29.14"
MatchType="wssp:Exact">
bBwPfItvKp3b6TNDq+14qs58VJQ=
</wssp:X509Extension>
</wssp:Claims>
</wssp:SecurityToken>
</wssp:KeyInfo>
<wssp:MessageParts
Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
wsp:Body()
</wssp:MessageParts>
</wssp:Confidentiality>
</wsp:Policy>
Note The test root certificate used above (CN=WSE2QuickStartServer) is one of the sample certs (certificates) that are installed with WSE 2.0. These sample certs used in this article are for demo purposes only and should not be used in production systems.
What the Confidentiality element in the policy file asserts is that a message meant for the SP must have the SOAP Body (wsp: Body) encrypted (wssp: Confidentiality wsp: Usage=wsp: Required) with the Public key of a X.509 digital certificate with Subject Name of "Root Agency" with a Key Identifier of "bBwPfItvKp3b6TNDq+14qs58VJQ=".
Now, we move on to the implementation of the service consumer (SC). This is a simple Windows Form with a TextBox and a Button control. The idea is to send the text entered in the TextBox as the sAccID parameter to the destination service via a request SOAP message. The destination service, SP returns the status SOAP message.
Listing 3
Dim svc As New securesoapnodechain.indexWse
svc.Url = "http://localhost/" + _
"dotnetproj/securesoapnodechain/sr1/sr1route.ashx"
'Find the Public Key of the SP's X509 Cert
Dim store As X509CertificateStore = _
X509CertificateStore.CurrentUserStore( _
X509CertificateStore.OtherPeople)
store.OpenRead()
Dim cert As X509Certificate = _
store.FindCertificateByKeyIdentifier _
(Convert.FromBase64String( _
"bBwPfItvKp3b6TNDq+14qs58VJQ="))(0)
Dim xTok As New X509SecurityToken(cert)
'Encrypting the Body of the SOAP Request message
svc.RequestSoapContext.Security.Elements.Add( _
New EncryptedData(xTok))
MessageBox.Show(svc.ProcessTransaction(TextBox1.Text))
The code snippet in Listing 3 shows the instantiation of a WSE-enabled XML Web service. Next, the certificate store is opened to retrieve the appropriate X.509 digital certificate public key of the intended receiver.
The next step is to instantiate an X509SecurityToken based on the retrieved X.509 digital certificate and encrypt the message body with this token. The RequestSoapContext.Security.Elements.Add (new EncryptedData (xTok)) code does this.
This generates a security header as shown in Listing 4.
Listing 4
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp
wsu:Id="Timestamp-f0a53573-189e-40b5-b0d6-a658a93354a7">
<wsu:Created>2004-11-19T06:15:07Z</wsu:Created>
<wsu:Expires>2004-11-19T06:20:07Z</wsu:Expires>
</wsu:Timestamp>
<xenc:EncryptedKey
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier ValueType=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
x509-token-profile-1.0#X509SubjectKeyIdentifier">
bBwPfItvKp3b6TNDq+14qs58VJQ=
</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>ypl3Fgm…WjDlUY170=</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference URI=
"#EncryptedContent-17900e98-b8e7-4e38-8f43-c7dbfb91c91d" />
</xenc:ReferenceList>
</xenc:EncryptedKey>
</wsse:Security>
This security header, which doesn't contain a SOAP actor or role, must not be removed prior to the final destination or endpoint. It contains the X509SubjectKeyIdentifier of the digital certificate that can successfully decrypt and retrieve an included generated symmetric key that, in turn, decrypts the SOAP body. The reason for this is that an asymmetric-encrypted message takes longer to decrypt than a symmetric one. Therefore, you get better performance by generating a symmetric key to encrypt a message, and then encrypting the symmetric key with an asymmetric one.
The SOAP Router and the ExtendedSecurity Function
We add more code to instruct WSE 2.0 to generate another security header that is meant for the next SOAP intermediate node whose actor attribute is set to the ActorNext value. The Soap.ActorNext returns a constant value that specifies the value for this attribute that is http://schemas.xmlsoap.org/soap/actor/next. A header block with this actor attribute is meant for the next intermediary in the chain. If the actor attribute is not present (as shown in Listing 4), then the extension element is intended for the ultimate recipient of the message. Listing 5 contains code that instructs the SecurityOutputFilter of the request pipeline to sign the SOAP headers and body with a username token that is meant for processing by SR1. This username token is then attached to this message so that it can be retrieved and referenced by the receiving node.
Listing 5
Dim uTok As _
New UsernameToken( _
"Softwaremaker", _
"SWMP@ssw0rd", _
PasswordOption.SendNone)
Dim SR1SecHeaderBlock As New _
Microsoft.Web.Services2.Security.Security
SR1SecHeaderBlock.Actor = Soap.ActorNext
SR1SecHeaderBlock.Tokens.Add(uTok)
SR1SecHeaderBlock.Elements.Add(New MessageSignature(uTok))
SR1SecHeaderBlock.MustUnderstand = True
svc.RequestSoapContext.ExtendedSecurity.Add( _
SR1SecHeaderBlock)
The main difference with the attachment of tokens and encryption of data in Listing 5 compared to the previous one is the use of the ExtendedSecurity property of the SOAP Context object to get the security headers that are not intended for the ultimate receiver of the SOAP message. The security header generated by this code block, which is meant for the next SOAP node, looks like this on the wire.
Listing 6
<wsse:Security soap:actor=
"http://schemas.xmlsoap.org/soap/actor/next"
soap:mustUnderstand="1">
<wsu:Timestamp
wsu:Id="Timestamp-c05b4ec3-9a07-4f38-99f8-a0d9fc663131">
<wsu:Created>2004-11-26T12:42:44Z</wsu:Created>
<wsu:Expires>2004-11-26T12:47:44Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken
wsu:Id="SecurityToken-bbe525cc-590a-4e31-adf1-17833dd2fb49">
<wsse:Username>Softwaremaker</wsse:Username>
<wsse:Nonce>MV/H/HTijROpfktikTGjiQ==</wsse:Nonce>
<wsu:Created>2004-11-26T12:42:44Z</wsu:Created>
</wsse:UsernameToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm=
"http://www.w3.org/2000/09/xmldsig#hmac-sha1" />
<Reference URI="#Id-0da291f0-b53e-4f88-9b44-16f80e6339f8">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>p9wijFWMhLFMY60Cq90Rus32uLk=</DigestValue>
</Reference>
<Reference URI="#Id-9bcb9201-5b43-4c57-9dfc-42d22c05c721">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>c+9e+gV4jod2ZiHrThXeCEViv9I=</DigestValue>
</Reference>
<Reference URI="#Id-a2082ea0-ab31-4271-9cbe-1dd77d98cac6">
...
</Reference>
</SignedInfo>
<SignatureValue>
wbEAbCW8sOl7BJ0I6pXI2GJ8fnM=
</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference
URI="#SecurityToken-bbe525cc-590a-4e31-adf1-17833dd2fb49"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-username-token-profile-1.0#UsernameToken" />
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
Notice from this security header in Listing 6 that:
- The soap: actor attribute is set to http://schemas.xmlsoap.org/soap/actor/next.
- The username token's password of the user is not transmitted in the message on the wire.
To summarize, the original SOAP message body that contains the confidential content is encrypted with the SP's X.509 digital certificate public key. Thereafter, the SOAP message headers and body is signed with the user's username token. Now we have two separate security headers targeted for separate recipients and an encrypted SOAP body that looks like this on the wire in Listing 7.
Listing 7
<soap:Body wsu:Id="Id-233598db-19fd-4b7f-8404-e16dc0836371">
<xenc:EncryptedData
Id="EncryptedContent-1a3c25b1-59cb-4c32-859e-2ba1d3402c58"
Type="http://www.w3.org/2001/04/xmlenc#Content"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
<xenc:CipherData>
<xenc:CipherValue>
KQugKBJOQ3W1mrUx4gnG13RYH...5nR8HsNH5K
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soap:Body>
The cipher value contained in this message body remains as-is, confidential, until it reaches the SP that plays the ultimate receiver role. No party or any intermediary node, whether trusted or not, will be able to decrypt the message body successfully without the asymmetric key. Only the holder of the corresponding private key, which is in the hands of the SP, can decrypt this cipher.
Another aspect to take note of is that the svc.Url is set to the value "http://localhost/dotnetproj/securesoapnodechain/sr1/sr1route.ashx". As shown previously in Figure 2, the SC knows only of the endpoint address of its adjacent SOAP node (SR1) to send messages to. The WSDL file of the SP can be retrieved off a Service Registry, a Universal Discovery Description Integration (UDDI) business registry node, or even from a proxy site of the SP. The WSDL file may contain a blank address location in the service element, or it may contain an address location different from the location of the WSDL file, as shown in Listing 8.
Listing 8
<service name="index">
<port name="indexSoap" binding="s0:indexSoap">
<soap:address location=
"http://localhost/dotnetproj/securesoapnodechain/
sr1/sr1route.ashx" />
</port>
</service>
The key point to remember is that the location of the WSDL file may be in a totally different location than the actual SP endpoint itself. Unlike the auto-generated WSDL file in Visual Studio .NET 2003, which automatically binds them to the same location, some of the practices of an enterprise is to maximize the reach of the WSDL file, but do the reverse for the SP endpoint. The WSDL is a contract document, but the SP's endpoint shouldn't be tightly bound. In this case, endpoints are allowed to change and move. This can be very useful in instances where SPs can be load-balanced across multiple separate site endpoints. This can also be a useful design to use when test-site services need to be moved to a production site and likewise. Instead of informing all SP-bound SCs of the new location, which can be a rather perplexing task if the SP is widely used, the actual destination endpoint could be retrieved from the UDDI or, in our case example, a referral intermediate SOAP router node could provide that endpoint translation service.
Note There are a couple of ways to override the auto-generation behavior of the WSDL file in Visual Studio .NET, and to abstract the location of the destination endpoint from the service consumer. In this example I have used the SoapExtensionReflector extension. You can refer to the attached project and source code for the example on how to do it.
In our example, the first intermediate SOAP node (SR1) provides the authentication service, while the second node (SR2) provides the URI Referral service.
SOAP Router 1 (SR1) performs a few critical roles in this entire sequence chain. It:
- Implements a custom username token manager to authenticate an incoming SOAP request. Since this username token works on a shared key concept, the SR1 can only successfully decrypt a message if it has the corresponding correct user password of the username.
- Adds a new security header targeted for the next SOAP node (SR2) with a custom SOAP output filter. A message signature embedded in this security header will identify itself to SR2.
- Performs a routing service to the next adjacent SOAP intermediate node (SR2).
Implementing and chaining them together within SR1 is easily done with WSE 2.0. The custom username token manager is shown in Listing 9. It shows a straightforward approach to retrieve a password from a user database based on the username. WSE 2.0 will load this custom username token manager type via a configuration file, and the returned password string is used to authenticate the sender of the message. If the returned password from the AuthenticateToken function doesn't match the password that was used to encrypt the message, a security credential exception is thrown and SR1 cannot decrypt the message.
Listing 9
Public Class CustomUsernameTokenManager
Inherits UsernameTokenManager
Protected Overrides Function AuthenticateToken( _
ByVal token As UsernameToken) As String
' Retrieve password from User database here
' based on token.Username
Return "SWMP@ssw0rd"
End Function 'AuthenticateToken
Once the above is successful, the security header meant for SR1 is removed. The cipher value of the SOAP message body still contains a cipher encrypted by the SP's Public Key only.
The addition of a new security header for SR2 is done through a custom SOAP output filter that is plugged into the outgoing pipeline. Listing 10 shows how to implement a custom outbound filter by inheriting your custom class from a SoapOutputFilter.
Listing 10
Public Class SWMOutputFilter : Inherits SoapOutputFilter
Public Overrides Sub ProcessMessage( _
ByVal envelope As SoapEnvelope)
'Find the Public Key of the Server's X509 Cert
Dim store As X509CertificateStore = _
X509CertificateStore.LocalMachineStore( _
X509CertificateStore.MyStore)
store.OpenRead()
Dim cert As X509Certificate = _
store.FindCertificateByKeyIdentifier _
(Convert.FromBase64String( _
"gBfo0147lM6cKnTbbMSuMVvmFY4="))(0)
Dim sr1xTok As New X509SecurityToken(cert)
Dim SR2SecHeaderBlock As New _
Microsoft.Web.Services2.Security.Security
SR2SecHeaderBlock.Actor = Soap.ActorNext
SR2SecHeaderBlock.Tokens.Add(sr1xTok)
SR2SecHeaderBlock.Elements.Add(New _
MessageSignature(sr1xTok))
SR2SecHeaderBlock.MustUnderstand = True
envelope.Context.ExtendedSecurity.Add(SR2SecHeaderBlock)
End Sub
End Class
Listing 10 instantiates a new X509SecurityToken based on another X.509 digital certificate to identify the SR2 with a Base64-encoded value for the Windows Key Identifier "gBfo0147lM6cKnTbbMSuMVvmFY4=". This token is used to sign the message header and body elements. The ExtendedSecurity property is again used to add a new security header for the next SOAP intermediary actor with the value of http://schemas.xmlsoap.org/soap/actor/next. Listing 11 shows this security header.
Listing 11
<wsse:Security soap:actor=
"http://schemas.xmlsoap.org/soap/actor/next"
soap:mustUnderstand="1">
<wsse:BinarySecurityToken ValueType=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-x509-token-profile-1.0#X509v3"
EncodingType=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-soap-message-security-1.0#Base64Binary"
wsu:Id="SecurityToken-9b579d70-4ba9-41eb-af09-ff71a1aaa3fa">
MIIBxDCCA…FdTRTJRd
</wsse:BinarySecurityToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm=
"http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#Id-1cd464c2-088a-40fb-93a9-363b7cc2d47b">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>LREoGJPI8lq8UobrwrKzl3ImgYM=</DigestValue>
</Reference>
<Reference URI="#Id-aae4a71a-8c44-4972-a860-eade6ac588ad">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>CuVcSoqRWttCq/HXrTuQKs69GhE=</DigestValue>
</Reference>
<Reference URI="#Id-48ee0db3-9ddf-44bd-b793-85ebd4a8d075">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>1HP/dkwolz7KwMr6i1YW9JlbYhY=</DigestValue>
</Reference>
<Reference URI="#Id-ca7864b6-ddef-4731-9bfe-1838a00328a9">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>IEFeRhwGtXYcqLbahW/qPr9Ik7E=</DigestValue>
</Reference>
<Reference URI="#Id-4f4f8a1e-e5c3-40c8-8389-b249b47ca0ac">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>g3a3xpPga2Yty1Re9xWFVDNNMY8=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>MfVVV4Q…fyv950 =</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference
URI="#SecurityToken-9b579d70-4ba9-41eb-af09-ff71a1aaa3fa"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-x509-token-profile-1.0#X509v3" />
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
To implement an output filter after inheriting from SoapOutputFilter, we need to register it with the outgoing pipleline.
We cannot plug this filter into the main outgoing pipeline of this intermediary node because response messages sent back to the SC also include this security header. Therefore, we have to tell WSE 2.0 to only include this new security header in the forward Request Pipeline. This is done by overriding the CreateForwardRequestPipeline() method of the SoapHttpRouter class shown in Listing 12.
Listing 12
Public Class RoutingHandler
Inherits Microsoft.Web.Services2.Messaging.SoapHttpRouter
Protected Overrides Function ProcessRequestMessage( _
ByVal message As SoapEnvelope) As Uri
Return New Uri( _
"http://localhost/" + _
"dotnetproj/securesoapnodechain/sr2/sr2route.ashx")
End Function
Protected Overrides Function CreateForwardRequestPipeline() _
As Pipeline
Dim p As New Pipeline
p.OutputFilters.Add(New sr1.SWMOutputFilter)
Return p
End Function
End Class
The CreateForwardRequestPipeline method creates a new request pipeline that acts on the forward request only. Returning response SOAP messages do not go through this pipeline when routed back to the SC. We added our custom SOAP output filter in Listing 10 to this new forward request pipeline. To finish the steps to this Routing Handler class, we need to override the ProcessRequestMessage that returns a URI to the next adjacent SOAP node.
We need to tell the .NET Framework how to handle these HTTP requests. This is done through ASP.NET HttpHandlers, where we must configure ASP.NET to map incoming HTTP requests for specific URLs to our custom routing handler. You register the handlers through the web.config file of SR1 shown in Listing 13. Any requests coming into this node with an extension of .ashx will be handled by our routing handler.
Listing 13
<httpHandlers> <add type="sr1.RoutingHandler, sr1" path="*.ashx" verb="*" /> </httpHandlers>
Now a SOAP envelope with two separate security headers is generated by WSE and dispatched to the next node, SR2. The earlier incoming request security header containing the signature of the username from the SC has been processed by SR1 and removed. One of the security headers is still meant for the ultimate receiver, SP. The other security header, which was added, is meant for its next intermediary, SR2. The SOAP message body still contains the cipher value that was encrypted with the public key of the SP.
Signature Checking and Verification
The last intermediary before the ultimate receiver is SOAP Router 2 (SR2), and it performs another set of critical roles in this entire sequence chain. It:
- Verifies that a message is signed by SR1 only
- Signs the message and extends the Security header
- Performs a routing referral service to the ultimate receiver (SP)
This piece of the SOAP routing chain uses another set of WSE 2.0 functionality. First SR2 checks for signatures in the incoming message (Authentication) and makes sure that a trusted party signed it (Trust). In this case, it must verify that the message is signed by SR1.
Due to certain known limitations of policy implementation in WSE 2.0, if SOAP messages contain an intermediary destination, WSE only enforces the policy for the ultimate destination. In other words, policies are not enforced for intermediaries. This is because policy maps messages to methods using the SOAP action. When body content routes a message, the policy mapping mechanism still relies on the SOAP action of the message, which is not enforced.
We can circumvent this by implementing our own message verification mechanisms, specifically signature checking and verification via code, as shown in Listing 14.
Listing 14
Public Class RoutingHandler
Inherits Microsoft.Web.Services2.Messaging.SoapHttpRouter
Protected Overrides Function ProcessRequestMessage( _
ByVal message As SoapEnvelope) As Uri
If VerifySignatureSender(message.Context) Then
Return MyBase.ProcessRequestMessage(message)
Else
Throw New SecurityFault( _
SecurityFault.InvalidSecurityTokenMessage, _
SecurityFault.MustUnderstandFaultCode)
End If
End Function
Private Function VerifySignatureSender( _
ByVal context As SoapContext) As Boolean
If IsMessageSigned(context) = False Then Return False
Dim secToken As SecurityToken
For Each secToken In context.Security.Tokens
If TypeOf secToken Is X509SecurityToken Then
Dim xSecToken As X509SecurityToken = _
CType(secToken, X509SecurityToken)
If xSecToken.Certificate.GetCertHashString = _
"CA7601381B4578502B62B8809825664F1E78DFA2" Then
Return True
End If
End If
Next
End Function
Private Function IsMessageSigned(ByVal context As SoapContext) _
As Boolean
Dim element As ISecurityElement
For Each element In context.Security.Elements
If (TypeOf (element) Is MessageSignature) Then
Dim sign As MessageSignature = element
If ((sign.SignatureOptions And _
SignatureOptions.IncludeSoapBody) <> 0) Then
Return True
End If
End If
Next
Return False
End Function
End Class
In the code above, we do a check on the message to make sure it is signed by SR1. The IsMessageSigned function is self-explanatory in that it checks to make sure there is a message signature element in the message. If there is a signature, the VerifySignatureSender function loops through the security tokens found in the message and checks for an X509SecurityToken that belongs to SR1.
An X.509 digital certificate contains a thumbprint that is a digest of the certificate data. To find out the thumbprint of a certificate, you can look at the Details tab of the certificate window and scroll to the Thumbprint property to get the hash value, as shown in Figure 3.

Figure 3. Hash value of a thumbprint
The string displayed in the lower window is your certificate's thumbprint that is a 40-digit hex number. You use this number (thumbprint) to verify that the certificate is genuine. The thumbprint will never fool you.
The GetCertHashString of the X509Certificate object returns the hash value for the X.509v3 certificate as a hexadecimal string that is the Thumbprint value.
Remember that it is one thing to validate digital signatures of a message; it is another to verify the author of the signature. This relates to Trust. A person in the middle could carry out an attack by replacing the signature and public key in the message security header with the attacker's own. This message could still be validated successfully at the receiving point even though it comes from an untrusted signee. Therefore, it is important to not just validate the signature, but also to verify the signee as well. In our case above, there is some out-of-band communication between the SR2 and the SR1 to exchange these thumbprint hash values.
Another way to accomplish the verification of identity is to implement a Challenge-Response system. A Challenge-Response is a common authentication technique whereby an individual is prompted via a challenge message to provide some private information in the response to the initiator. There are many ways to devise this Response-Challenge system. One key advantage of a Challenge-Response system is that it allows for the dynamic verification of many different identities without resorting to any out-of-band communication technique. You can introduce additional steps to increase the security, such as introducing timeout elements. However, if performance is a key constraint, an out-of-band communication technique that minimizes frequent network calls may be a better option.
Note The Response-Challenge system is beyond the scope of this article.
The step to implement after the signee has been verified is harder. In our example, the incoming message policy of SP states that it must be signed with the private key of SR2. Listing 15 shows a snippet of the policy file that asserts that the incoming message must be signed by the private key of SR2.
Listing 15
<wsp:Policy wsu:Id="#Sign-X.509-Encrypt-X.509">
<wssp:Integrity wsp:Usage="wsp:Required">
<wssp:TokenInfo>
<wssp:SecurityToken wse:IdentityToken="true">
<wssp:TokenType>
http://docs.oasis-open.org/wss/2004/01/oasis-200401
-wss-x509-token-profile-1.0#X509v3
</wssp:TokenType>
<wssp:TokenIssuer>CN=Root Agency</wssp:TokenIssuer>
<wssp:Claims>
<wssp:SubjectName MatchType="wssp:Exact">
CN=WSE2QuickStartClient
</wssp:SubjectName>
<wssp:X509Extension OID="2.5.29.14"
MatchType="wssp:Exact">
gBfo0147lM6cKnTbbMSuMVvmFY4=
</wssp:X509Extension>
</wssp:Claims>
</wssp:SecurityToken>
</wssp:TokenInfo>
<wssp:MessageParts
Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
wsp:Body()
wsp:Header(wsa:Action)
wsp:Header(wsa:FaultTo)
wsp:Header(wsa:From)
wsp:Header(wsa:MessageID)
wsp:Header(wsa:RelatesTo)
wsp:Header(wsa:ReplyTo)
wsp:Header(wsa:To)
wse:Timestamp()
</wssp:MessageParts>
</wssp:Integrity>
</wsp:Policy>
The Integrity element in the policy indicates in the security assertion the message body and the message element parts of the addressing and security headers that need to be signed.
Normally, it would not be a concern if the message-initiating actor needs to implement this signature. However, by the time SR2 receives this message, it faces a couple of major obstacles:
- Each SOAP node needs to be a good neighbor and can only process SOAP headers meant for it. In this case, SR2 cannot have access to the security header (with the actorless attribute) that is targeted at the ultimate receiver.
- SR2 should not create another security header targeted at the ultimate receiver, which in our case is the SP, because the SP will fail at processing multiple security headers if it is not certain of the order they were added. As stated in the WS-Security specifications, security headers can be re-ordered by intermediaries. However, there are no rules governing the order in which headers are processed. Security headers will generally have ordering dependencies. As such, there should not be two security headers for the same node.
Let me try to illustrate Point 2 here:
It is important to note that SOAP headers can be re-ordered by intermediaries. Therefore, a SOAP message that was sent out as follows:
<soap:Header> <header1>...</header1> <header2>...</header2> </soap:Header>
can be re-ordered by processing intermediaries and thus can be received looking like this:
<soap:Header> <header2>...</header2> <header1>...</header1> </soap:Header>
SOAP endpoints should still be able to process the message, as there are no rules governing the order in which headers are processed.
However, security headers generally do have some form of ordering dependencies. If the message body is to be signed first before encryption, the message receiver needs to know the order of the process so it can carry out the decryption first before signature verification. Thus, if a SOAP message is received as follows:
<soap:Header>
<wsse:Security actor="http://softwaremaker.net/William Tay">
<!--Signature Elements-->
</wsse:Security>
<wsse:Security actor="http://softwaremaker.net/William Tay">
<!--Encryption Elements-->
</wsse:Security>
</soap:Header>
and the receiver is not certain of processing the two security headers in the order they were added, this message is subject to failure.
With the above constraints, we can safely conclude that there cannot be two security headers for the same node. It would be better if we prepend the security elements into a security header targeted at the receiver, so the receiver is more certain of the ordering process within the security header.
<soap:Header>
<wsse:Security actor="http://softwaremaker.net/William Tay">
<!--Encryption Elements ->
<!--Signature Elements ->
</wsse:Security>
</soap:Header>
We will not be able to access the security header elements of the SP at this intermediary via the SoapContext because it only gives you the security headers for the current intermediary. If we need to access other security headers for SOAP nodes further along the path, we must unpick the SOAP envelope message itself. However, you shouldn't be doing this unless you're planning to extend the header with additional information—and that is precisely what we are trying to do.
Security Header Extensions
Fortunately, WSE 2.0 extensibility allows us to do so, and it provides sufficient building blocks to help us construct other solutions. Before moving on to extend the security header with additional information, let's revisit the WS-Security specifications again to determine how it needs to be done so that it is compliant.
WS-Security specifications extension requirements
- As elements are added to a <wsse:Security> header block, they SHOULD be prepended to the existing elements. As such, the <wsse:Security> header block represents the signing and encryption steps the message producer took to create the message. This prepending rule ensures that the receiving application can process sub-elements in the order they appear in the <wsse:Security> header block, because there will be no forward dependency among the sub-elements. Note that this specification does not impose any specific order of processing the sub-elements. The receiving application can use whatever order is required.
- If a producer wishes to sign a message before encryption, then following the ordering rules laid out in section 5, "Security Header", they SHOULD first prepend the signature element to the <wsse:Security> header, and then prepend the encryption element, resulting in a <wsse:Security> header that has the encryption element first, followed by the signature element:
<wsse:Security> header
[encryption element]
[signature element]
- If a producer wishes to sign a message after encryption, they SHOULD first prepend the encryption element to the <wsse:Security> header, and then prepend the signature element. This will result in a <wsse:Security> header that has the signature element first, followed by the encryption element:
<wsse:Security> header
[signature element]
[encryption element]
- For processing efficiency it is RECOMMENDED to have the signature added and then the security token prepended so that a processor can read and cache the token before it is used.
The key points noted above will be useful when we extend the security header for another node.
In our example, we are going to extend the security header by signing the header elements and message body. Since the message body was previously encrypted by the SC for the SP, there exists an [encryption element] within this security header. We need to prepend our [signature element] to the existing [encryption element]. See point [3] above of the WS-Security specification extension requirements.
Listing 16 is divided into three main steps. First, pick the security header meant for the SP; create the signature elements and the binary security token; and finally, insert them into the security header.
Listing 16
Private Function PrependSignatureAndToken( _
ByVal env As SoapEnvelope) As SoapEnvelope
Dim nsmgr As New XmlNamespaceManager(env.NameTable)
Dim num1 As Integer = 0
Dim n As XmlNode
Dim nl As XmlNodeList
Dim store As X509CertificateStore
Dim cert As X509Certificate
Dim SWMCustomSecContainer As New _
Microsoft.Web.Services2.Security.Security
Dim mainElement, sxe, txe As XmlElement
nsmgr.AddNamespace( _
XmlSignature.Prefix, XmlSignature.NamespaceURI)
nsmgr.AddNamespace(XmlEncryption.Prefix, _
XmlEncryption.NamespaceURI)
nsmgr.AddNamespace(WSSecurity.Prefix, _
WSSecurity.NamespaceURI)
nsmgr.AddNamespace(WSUtility.Prefix, WSUtility.NamespaceURI)
nsmgr.AddNamespace(Soap.Prefix, Soap.NamespaceURI)
nl = env.Header.SelectNodes("./wsse:Security", nsmgr)
' Look for the Security Header Element meant for the
' Ultimate Receiver (Actorless Attribute)
For Each n In nl
If n.SelectSingleNode("./@soap:actor", nsmgr) Is Nothing _
Then
mainElement = DirectCast(n, XmlElement)
Exit For
Else
Throw New Exception( _
"No Actorless Attribute for the Ultimate Receiver")
End If
Next
'Find the Public Key of the Server's X509 Cert
store = _
X509CertificateStore.LocalMachineStore( _
X509CertificateStore.MyStore)
store.OpenRead()
cert = store.FindCertificateByKeyIdentifier _
(Convert.FromBase64String( _
"gBfo0147lM6cKnTbbMSuMVvmFY4="))(0)
Dim sr2xTok As New X509SecurityToken(cert)
'Preparing the Security Header
SWMCustomSecContainer.Tokens.Add(sr2xTok)
'Prepare the Signatures
Dim SWMCustomSignature As New MessageSignature(sr2xTok)
SWMCustomSignature.OwnerElement = mainElement
'Assign Security Container
SWMCustomSignature.Container = SWMCustomSecContainer
'Prepare the crucial Signature and Token Elements
SWMCustomSignature.Document = DirectCast(env, XmlDocument)
SWMCustomSignature.ComputeSignature()
'For Signatures
sxe = SWMCustomSignature.Signature.GetXml(env)
'For BinarySecurityToken
txe = SWMCustomSignature.SigningToken.GetXml(env)
'Insert those nodes here now into the Security Header
'Security Elements are to be PRE-pended to Existing ones –
'Order is crucial here.
'Find the First Node
n = mainElement.SelectSingleNode("./xenc:* | ./ds:*", nsmgr)
'Prepend to the first existing node - ORDER is Important
mainElement.InsertBefore(sxe, n)
'Append Security Tokens now
nl = mainElement.SelectNodes("./wsse:*", nsmgr)
If nl.Count = 0 Then 'No Tokens in there
n = mainElement.SelectSingleNode("./xenc:* | ./ds:*", _
nsmgr)
mainElement.InsertBefore(txe, n)
Else
mainElement.InsertAfter(txe, nl(nl.Count - 1))
End If
Return env
End Function
As I explained earlier, we will not be able to use the SOAPContext object to access the security header elements meant for the SP at this intermediary; therefore, I unpick the SOAP envelope passing through this SOAP intermediary through the SoapOutputFilter. I override the ProcessMessage subroutine that gives us access to the SOAP message through the SOAP envelope parameter. This is where I will edit and manipulate the security header on the outgoing SOAP message through a custom PrependSignatureAndToken function. We will further dissect the PrependSignatureAndToken function shown in Listing 16.
It starts with the initialization of a XmlNamespace Manager. The XmlNamespaceManager is a class that is designed to resolve, add, and remove namespaces from a collection, and provide namespace scope management for the namespaces. You can create an XmlNamespaceManager class whenever you want to hold XML namespaces in a collection, and the collection associates the namespace prefix and their URIs. WSE loads the entire SOAP envelope into a XML DOM. I use the SelectNodes method and pass in an XPath expression together with the XmlNamespaceManager to sift through the envelope header for the <wsse:Security> element. This returns an XmlNodeList that I will further filter down to look for an actorless attribute security header element. Once it is found, I cast it to an XmlElement object for use later.
Next, we create an X509SecurityToken from a digital certificate with a Base64 Windows Key Identifier ("gBfo0147lM6cKnTbbMSuMVvmFY4="). This security token is used to sign the message as required by the SP's incoming security policy. We attach this X509SecurityToken to a newly instantiated security header that we assign as the container of a newly instantiated MessageSignature object. The container property gets the Security header that contains this security element. We further assign the earlier retrieved <wsse:Security> XmlElement to the OwnerElement property of the MessageSignature. This tells WSE which security header element owns the element being signed. Another important reason for setting this OwnerElement property is that only the Timestamp element belonging to the WS-Security header is signed if the SignatureOptions.IncludeTimestamp enumeration is selected. Setting the OwnerElement property to the WS-Security header XML element also ensures that the correct Timestamp of the security header is signed.
We are ready to prepare the SOAP envelope for digital signing and create the necessary signature security elements. We set the Document property of the MessageSignature to the SOAP envelope that we unpick from the ProcessMessage function parameter. This is the raw SOAP message that would be dispatched through the outgoing pipeline, and is the message that needs to be signed. We then compute the signature.
We need to extract two important signature elements from the MessageSignature after the ComputeSignature method has been executed. They are:
- The <Signature> element containing the <SignedInfo>, <SignatureValue> and <KeyInfo> child elements. Nested within the <SignedInfo> element are the <CanonicalizationMethod> and <SignatureMethod> elements together with the <Reference> elements containing the URI attributes of the elements being signed and their corresponding DigestValue. The <SignatureValue> element carries the value of the encrypted digest of the <SignedInfo> element. All of these elements are generated from the Signature function, which returns a SignedXMLSignature. Listing 17 shows the abbreviated output of the SignedXMLSignature.
Listing 17
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <Reference URI="#Id-dfba618a-7fec-405c-b82d-8432b0ef6571"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue>H7/ZtdKpo2t9rwyaVBr0am+T1i8=</DigestValue> </Reference> <Reference URI="..."> </Reference> </SignedInfo> <SignatureValue> OCjZM301Crl4BSXCEYb3Qg5yZtQ...gR23Nj62caxy0so= </SignatureValue> <KeyInfo> <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/ oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:Reference URI= "#SecurityToken-158812b9-c482-4fd8-9077-39ed2a15ddf9" ValueType="http://docs.oasis-open.org/wss/2004/01/ oasis-200401-wss-x509-token-profile-1.0#X509v3" /> </wsse:SecurityTokenReference> </KeyInfo> </Signature> - The <wsse:BinarySecurityToken> element carries the X509 digital certificate used to sign the message. This element is generated by the SigningToken functions of the MessageSignature and the abbreviated output is shown in Listing 18.
Listing 18
<wsse:BinarySecurityToken ValueType="http://docs.oasis-open.org/wss/2004/01/ oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis -200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-ed242d72-3cc9-47ac-9233-c8bf6446fafb" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-secext-1.0.xsd"> MIIBxDCCAW6gA...BY5sT/txBnVJGziyO8DPYdu2fPMER8ajJfl </wsse:BinarySecurityToken>
With the two security signature elements at hand, we are now ready to insert them into the SOAP envelope. Care must be taken, as order is important as stated in the WS-Security specifications. Please see points [2] and [3] of the WS-Security specification extension requirements.
We run the SelectSingleNode function on the <wsse:Security> XmlElement object which returns the first XmlNode that matches the XPath expression passed into the function parameter. The XPath query expression searches for any [signature element] or [encryption element] and returns the first node it finds. It is interesting to note that I am using the union operator in the XPath query that returns the union of one or more location paths. The expression "./xenc:* | ./ds:*" returns the entire [encryption element] and [signature element] nodes. It preserves the order of the elements and does not return any duplicates. Once we find the first node, we prepend our <Signature> element to it by issuing an InsertBefore method that inserts the specified node immediately before the specified reference node.
Next we insert the <wsse:BinarySecurityToken> element into the security headers. Tokens are controlled with a dependency graph, and thus the order of tokens in the message does not always follow the order in the code, and is not required to do so. However, as stated in the WS-Security specifications, the order of the signing and encryption elements is the only thing that must be preserved. The order of the tokens is only relevant in the sense that a token used by a signing or encryption operation must appear before that operation (where use means the key of the token is used, or the token is the target of a signing or encryption operation). Thus, tokens will always appear before the signature or encryption step that uses them. See point [4] of the WS-Security specification extension requirements.
We need to find out if there are any security tokens in the security header. If there are, we will append it to the last found security token; if not, we will prepend it to the first found [encryption element] or [signature element] node.
The extended security header of the returned envelope will look something like this in Listing 19.
Listing 19
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp
wsu:Id="Timestamp-4ae1c43a-5666-4cdf-a346-a37a5631fc4c">
<wsu:Created>2004-12-05T03:21:23Z</wsu:Created>
<wsu:Expires>2004-12-05T03:26:23Z</wsu:Expires>
</wsu:Timestamp>
<wsse:BinarySecurityToken
ValueType="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-x509-token-profile-1.0#X509v3"
EncodingType="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-soap-message-security-1.0#Base64Binary"
wsu:Id="SecurityToken-075a547c-106c-4a81-8494-f029fc54150c">
MIIBxDCCAW6gAwIBAg...MER8ajJfl
</wsse:BinarySecurityToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod
Algorithm=
"http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#Id-dfba618a-7fec-405c-b82d-8432b0ef6571">
<Transforms>
<Transform Algorithm=
"http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>H7/ZtdKpo2t9rwyaVBr0am+T1i8=</DigestValue>
</Reference>
<Reference URI="...">
</Reference>
</SignedInfo>
<SignatureValue>
OCjZM301Crl4BSXCEYb3Qg5yZtQ...gR23Nj62caxy0so=
</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Reference
URI=
"#SecurityToken-075a547c-106c-4a81-8494-f029fc54150c"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-x509-token-profile-1.0#X509v3" />
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
<xenc:EncryptedKey
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier
ValueType=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">
bBwPfItvKp3b6TNDq+14qs58VJQ=
</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>ypl3Fgm…WjDlUY170=</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference
URI=
"#EncryptedContent-17900e98-b8e7-4e38-8f43-c7dbfb91c91d" />
</xenc:ReferenceList>
</xenc:EncryptedKey>
</wsse:Security>
Note the inclusion of the <wsse:BinarySecurityToken> and the <Signature> elements into the security header that are meant for the SP.
As you can infer, we have extended the incumbent security header in our own SoapOutputFilter. Once we plug this output filter into the outgoing pipeline, the SOAP envelope will contain this extended security header targeted at the SP.
Finally, we need to route the message to the ultimate receiver, the SP. We make use of the WSE 2.0 Referral Cache to do so via a configuration file, as seen in Listing 20.
Listing 20
<r:referrals
xmlns:r="http://schemas.xmlsoap.org/ws/2001/10/referral">
<r:ref>
<r:for>
<r:exact>
http://localhost/dotnetproj/securesoapnodechain/
sr1/sr1route.ashx
</r:exact>
</r:for>
<r:go>
<r:via>
http://localhost/dotnetproj/securesoapnodechain/index.asmx
</r:via>
</r:go>
<r:refId>uuid:2c4a0ba5-b93b-4219-a1d0-6c0ace55c598</r:refId>
</r:ref>
</r:referrals>
The ProcessRequestMessage of the base HttpSoapRouter class will refer to this referral configuration file and route messages for any SOAP actor name matching the SOAP actor "http://localhost/dotnetproj/securesoapnodechain/sr1/sr1route.ashx" via "http://localhost/dotnetproj/securesoapnodechain/index.asmx", which is the address location of the ultimate receiver in this case.
The abbreviated request message sent from SR2 to the SP looks like this on the wire.
Listing 21
<soap:Envelope
xmlns:soap=http://schemas.xmlsoap.org/soap/envelope/...>
<soap:Header>
...
<wsa:To wsu:Id="Id-9e410944-9828-4210-98ee-455f873333a9">
http://localhost/dotnetproj/securesoapnodechain/
sr1/sr1route.ashx
</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp
wsu:Id="Timestamp-4ae1c43a-5666-4cdf-a346-a37a5631fc4c">
<wsu:Created>2004-12-05T03:21:23Z</wsu:Created>
<wsu:Expires>2004-12-05T03:26:23Z</wsu:Expires>
</wsu:Timestamp>
<wsse:BinarySecurityToken
ValueType=
"http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-x509-token-profile-1.0#X509v3"
EncodingType=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-
wss-soap-message-security-1.0#Base64Binary"
wsu:Id=
"SecurityToken-075a547c-106c-4a81-8494-f029fc54150c">
MIIBxDCCAW6gAwIBAg...MER8ajJfl
</wsse:BinarySecurityToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod
Algorithm=
"http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod
Algorithm=
"http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference
URI="#Id-dfba618a-7fec-405c-b82d-8432b0ef6571">
<Transforms>
<Transform
Algorithm=
"http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm=
"http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>
H7/ZtdKpo2t9rwyaVBr0am+T1i8=
</DigestValue>
</Reference>
<Reference URI="...">
</Reference>
</SignedInfo>
<SignatureValue>
OCjZM301Crl4BSXCEYb3Qg5yZtQ...gR23Nj62caxy0so=
</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference
xmlns:wsse=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
wssecurity-secext-1.0.xsd">
<wsse:Reference
URI=
"#SecurityToken-158812b9-c482-4fd8-9077-39ed2a15ddf9"
ValueType=
"http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-x509-token-profile-1.0#X509v3" />
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
<xenc:EncryptedKey
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod
Algorithm=
"http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier
ValueType=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-
x509-token-profile-1.0#X509SubjectKeyIdentifier">
bBwPfItvKp3b6TNDq+14qs58VJQ=
</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>
ypl3Fgm…WjDlUY170=
</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference
URI="#EncryptedContent-17900e98-b8e7-4e38-8f43-c7dbfb91c91d" />
</xenc:ReferenceList>
</xenc:EncryptedKey>
</wsse:Security>
</soap:Header>
<soap:Body wsu:Id="Id-233598db-19fd-4b7f-8404-e16dc0836371">
<xenc:EncryptedData
Id="EncryptedContent-1a3c25b1-59cb-4c32-859e-2ba1d3402c58"
Type="http://www.w3.org/2001/04/xmlenc#Content"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
<xenc:CipherData>
<xenc:CipherValue>
KQugKBJOQ3W1mrUx4gnG13RYH...5nR8HsNH5K
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soap:Body>
</soap:Envelope>
Note the following in the message:
- The SOAP message body still contains the cipher value that was initially encrypted with the public key of the SP. In fact, the cipher has maintained the same value throughout its transmission through the SOAP nodes.
- The addressing headers still points to "http://localhost/dotnetproj/securesoapnodechain/sr1/sr1route.ashx". The physical address of the ultimate receiver is reached via relay routing from the other nodes.
- All other security headers meant for the SOAP intermediaries were processed and removed by the appropriate respective nodes.
- The security header without the SOAP actor or role is not removed prior to the final destination or endpoint.
- The security header without the SOAP actor or role meant for the SP is extended to include additional elements so that it can meet the incoming policy requirements of the SP. The additional elements adhered strictly to the ordering requirements of the WS-Security specifications with the [signature element] being prepended to the [encrypted element] and the <wsse:BinarySecurityToken> being inserted into the security header before its respective security operation.
- The encrypted content of the SOAP message body remains confidential throughout the whole routing and dispatching process and can only be decrypted by the SP.
The final message will undergo processing from the incoming filters of the SP request pipeline where the SP's private key will decrypt the SOAP message body. Then the ProcessTransaction WebMethod of ASMX takes over and returns an appropriate response that goes back through the same SOAP nodes again in a reverse order via an HTTP back channel.
As we can see from this example, the concept of the "Next-Hop" routing is practiced, as each node of the entire routing chain has no idea of the location of any other node except its adjacent one. Each node is a good neighbor and only processes what is meant for it and ignores the others. Each node can also input new security headers into the message to identify and verify itself or to further encrypt the message body, if it chooses to, before it sends the message to the next intermediary. This can be easily done with the ExtendedSecurity function of WSE 2.0.
If a security header not meant for the intermediary needs to be manipulated for whatever reason, picking at the SOAP message that traversed through the intermediary can extend it. I have shown how WSE 2.0 makes it easy for us to be able to add signatures or even additional encryption elements into the security header, if we choose to. However, please take extra care in implementing the ordering of the newly added security elements so that it complies with the standard WS-Security specifications.
While I have introduced some complexity into the intermediary nodes by using the SOAP router model, some people may want to implement a Gateway model instead. Although I have shown that it can be done, complex processing that does more than forwarding of messages may be more suited for a gateway. You can implement the gateway as something other than an ASMX. For example, it could be implemented as an extended form of the WSE router (i.e., an IHttpHandler). A gateway model doesn't have to support only a single final endpoint; it can support many since it doesn't have to be message-schema aware.
Conclusion
This article demonstrates some of the many cool features that can be developed and implemented very easily with WSE 2.0. More enterprise are adopting the use of Web services, and with the maturing of WS-Security, we are seeing adoption and use of Web services beyond the DMZ of the corporate organizational boundaries. I am seeing a rise in routing requirements of SOAP messages that can solve many potential business problems. Examples of problems that can be solved by this example include scenarios where:
- The SC and the SP have no trust for each other and thus need trusted intermediaries to route messages across.
- In some cases, the SP may reside on a different sub-domain from the SC and will therefore need intermediaries to route messages across.
- In good service-orientation principles, the SP should be built with no integration of the SC in mind, and would therefore need other trusted intermediaries to relay messages to complete certain business processes and functions.
- There is a need to perform authentication or other infrastructure services that are not specific to the business service itself. Sometimes, different parties may own these services. Designing and implementing a routing pattern such as the one explained above promotes the good practice of separating business concerns. This will aid tremendously in extensibility and scalability of the entire system application.
- There is a need to abstract the ultimate receiver's location endpoint from the consumer.
What alternatives and options you can think of to solve business problems with XML Web services depend on how far your imagination takes you. For example, you may also want to implement another secure channel for the response messages back to the SC, or you may want the response messages to take on a different route through other intermediaries before returning it back to the SC again, or you may even want to implement your own transport scheme and do some cool asynchronous duplex messaging through other network protocols. WSE 2.0 offers many extensibility features that make implementing these solutions easier.
Be careful, though. The interoperability and flexibility of Web services does bring a penalty of latency and performance. Thus, it is very important to draw the explicit service boundaries well and design those service interfaces with proper thought processes and guidelines to achieve maximum business process reuse without putting a drag on resources.
After all, when business wins, Web services win.
William Tay is a Senior Consultant/Enterprise Software Architect by day with NCS Pte. Ltd., a premier APAC Systems Integrator based in Singapore. His interests include areas of object-oriented analysis and programming, distributed computing, and adoption and implementation of XML services. By night, William becomes a house-husband and still scrubs everything with SOAP.
He has done research, development, and implementation work in applying Web services in environments of service orientation.
Besides being a Microsoft Most Valuable Professional (MVP) as a Visual Developer - Solutions Architect, William is also a Microsoft Regional Director who writes and speaks frequently in MSDN circuits, Microsoft TechEd, and other Microsoft events, as well as academic and industrial Web services events. He is also a speaker of the INETA Speakers Bureau and contributes a fair amount of his time to community work in helping professionals in the software development field.