Encrypting SOAP Messages Using Web Services Enhancements

 

Jeannine Hall Gailey
Web Consultant

March 2003

Applies to:
   SOAP Messaging
   Microsoft&174; Web Services Enhancements (WSE) 1.0 SP1
   Microsoft ASP.NET
   WS-Security Specification
   X.509 Certificates
   XML Encryption Specifications

Summary: How to encrypt SOAP messages over standard HTTP using Microsoft Web Services Enhancements (WSE) that supports the WS-Security specification. Includes a review of how encryption works for SOAP messages and how it is defined in the WS-Security and XML Encryption specifications. (22 printed pages)

Contents

Introduction to the WSE
Security Features in WSE
Encrypting SOAP Messages
Encryption Support in WSE
Configuring Web Services Enhancements
Symmetrically Encrypting a SOAP Message
Using X.509 Certificates to Encrypt a SOAP Message
Selecting the Message Elements to Encrypt
Limitations and Interoperability Issues
Conclusion

Introduction to the WSE

In order to drive Web service interoperability in the enterprise, a new generation of Web services specifications is being proposed that should improve interoperability in areas that are crucial for Web services such as security, reliable messaging and sending attachments. To support these proposed standards, Microsoft has released Web Services Enhancements (WSE) 1.0 SP1, which includes a set of classes that implement these new protocols as well as a set of filters hosted by Microsoft® ASP.NET that intercept incoming and outgoing SOAP messages and interpret or generate the SOAP headers to support the requested functionalities. WSE provides support for the following specifications:

  • WS-Security and Web Services Security Addendum
  • WS-Attachments and DIME
  • WS-Routing
  • WS-Referral

For more general information on using WSE, see Web Services Enhancements for Microsoft .NET and Programming with Web Services Enhancements 1.0 for Microsoft .NET.

Security Features in WSE

In the WSE runtime, the work of generating and reading WS-Security-compliant SOAP headers is done by a set of filters. When a SOAP message is received at a WSE-enabled Web server, the SOAP message is passed through a series of these input filters that read the WS-* compliant SOAP headers, modify them if necessary, and generate a related set of programming objects. In the same way, outgoing messages pass through a set of output filters that serialize certain headers as defined programmatically by the WSE objects. For more information on how these pipelines work, see Inside the Web Services Enhancements Pipeline.

All of the WS-Security features supported by WSE 1.0 SP1 are implemented by the security input and output filters that implement the SecurityInputFilter and SecurityOutputFilter objects, which include the following:

  • Digital Signatures
  • Encryption
  • Signing and encrypting with username tokens
  • Signing and encrypting with X.509 certificates
  • Signing and encrypting with custom binary tokens

My focus is on XML encryption using both a shared secret and X.509 certificates.

For more information on WSE support for tokens, certificates, and authentication in general, check out WS-Security Authentication and Digital Signatures with Web Services Enhancements. For more general information on the WS-Security specification, see Understanding WS-Security.

Encrypting SOAP Messages

Transmitting data over the wire in any standardized format makes it easy for a malicious user to access your valuable data should it be intercepted. Transmitting data using SOAP and XML could not only potentially compromise data, information about the internal workings of your Web service might be discovered based on the XML schema shown in the SOAP message itself. By using an appropriate encryption algorithm, this data and message structure can be fully protected. Encryption is simply the process of performing a reversible algorithm to transform sensitive data using a special key so that the data cannot be read without being unencrypted—using either a copy of the original key or a key derived from the original—depending on the type of encryption being used.

To date, the most common forms of Internet encryption have involved using a transport-level encryption scheme such as IPSEC or SSL, which encrypt at the transport level. While certainly secure, transport-level encryption can impact performance, particularly when only a portion of the SOAP message needs to be encrypted. Also, transport-level encryption does not allow you to route a message securely using a Web service intermediary, since the message would need to be decrypted by the intermediary before being forwarded to the ultimate receiver using a new encrypted stream. Fortunately, WS-Security specifies a way to leverage functionalities of the XML Encryption protocol to encrypt only the sensitive parts of a SOAP message so that this data can remain secure until the message reaches the ultimate receiver.

How XML Encryption Works

The XML Encryption protocol specifies that part or all of the body of a SOAP message can be encrypted. When XML encryption is used, an encryption algorithm is run against the portion of the XML document being encrypted and this XML data is replaced with the resulting encrypted data inside an EncryptedData element. WS-Security, which is based on XML Encryption, further stipulates that when XML encryption is used in a SOAP message, the location of the resulting EncryptedData element is referenced from the Security header element. If multiple elements in the body of the message are encrypted, then each is referenced by a separate ReferencedData element in the ReferenceList.

For an EncryptedData element, a hint at the encryption key used can be specified in the KeyInfo element, and the encryption algorithm can be specified in the EncryptionMethod element. The KeyInfo element is defined in the XML Signature specification.

Example of an Encrypted SOAP Message

The following SOAP message example has a Payment element that contains sensitive customer information:

<soap:Envelope soap:xmlsn="http://www.w3.org/2002/12/soap-envelope">
  <soap:Header>
  ...
  </soap:Header>
  <soap:Body>
  ...
    <x:Order Type="Purchase" x:xmlns="http://example.com/order">
      <x:Payment Type="CreditCard">
        <x:CreditCard Type="Visa">
          <x:CardNumber>123456789123456</CardNumber>
          <x:ExperationDate>1108</ExperationDate>
        </x:CreditCard>
      </x:Payment>
      ...
    </x:Order>
    ...
  </soap:Body>
</soap:Envelope>

Since the Payment element contains sensitive data, it should be encrypted. The following example shows the same message, but with the Payment element replaced by an EncryptedData element containing the encrypted form of the Payment element. This EncryptedData element is referenced by the DataReference element in the Security header.

<soap:Envelope soap:xmlsn="http://www.w3.org/2002/12/soap-envelope"
  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
  xmlns:xsig="http://www.w3.org/2000/09/xmldsig#"
  xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/04/secext">
  <soap:Header>
    <wsse:Security>
      <xenc:ReferenceList>
        <xenc:DataReference URI="#OrderID"/>
      </xenc:ReferenceList>
    </wsse:Security>  ...
  </soap:Header>
  <soap:Body>
  ...
    <x:Order Type="Purchase" x:xmlns="http://example.com/order">
      <xenc:EncryptedData Id="OrderId">
        <xenc:EncryptionMethod 
          Algorithm= "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
        <xsig:KeyInfo>
          <xsig:KeyName>My Symmetric Key</xsig:KeyName>
        </xsig:KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>...</CipherValue>
        </xenc:CipherData>
      </xenc:EncryptedData>
    ...
    </x:Order>
    ...
  </soap:Body>
</soap:Envelope>

Of course, in this example Web service you should also digitally sign the message to prevent a malicious party from tampering with the data and include a timestamp or unique identifier to be able to detect a replay attack. For more information on using WSE to sign SOAP messages, see WS-Security Authentication and Digital Signatures with Web Services Enhancements.

Types of Encryption

Keep in mind that encryption algorithms can be divided into two major types: symmetric (shared secret) and asymmetric (public-key). In symmetric encryption, a secret encryption key is shared between the two parties involved in the secure message exchange. The sender uses a copy of this private key to reversibly encrypt the data. At the other end of the wire, the recipient uses an identical copy of the private key to decrypt the data. Most encryption, based on shared secrets such as passwords and shared security tokens, are examples of symmetric encryption. The Kerberos Network Authentication Protocol used today by Microsoft Windows-based products is an example of symmetric key-based security protocol. In this type of system, a central server distributes the shared keys to the parties that need to interact securely. The drawbacks to symmetric encryption are related to management of the shared secret keys, distributing them and keeping them safe, especially on public networks like the Internet.

Asymmetric encryption was designed to solve some of these key management challenges in public networks by using key pairs instead of shared keys. In asymmetric encryption, each party has both a private key and a public key. The public key is generated by a cryptographically irreversible operation on the private key so that if one of the keys is used to encrypt data, the other can be used to decrypt it. In addition, the value of the private key cannot be determined from the public key, and only the private key can be used to decrypt data encrypted with the related public key, and vice versa. When sending asymmetrically encrypted messages, the sender encrypts the message with the recipient's public key, which ensures that only the recipient's private key can be used to decrypt the message. If you did it the other way, anyone would be able to decrypt the data using the publicly available key. Asymmetric encryption is the basis for this Public Key Infrastructure (PKI) that is the basis for the X.509 security standard. Asymmetric encryption algorithms are typically based on algorithms that process very large numbers, such as exponential and logarithmic operations. They require much more processor time for encryption and decryption than symmetric algorithms. Because of this, asymmetric encryption is often used to safely transport a symmetric "session" key that is used to encrypt the rest of the conversation and that is only valid for the duration of the exchange.

Since public keys can be readily obtained, using public key encryption alleviates the difficulties of distributing and managing shared secrets. Unfortunately, the price for this convenience is that asymmetric algorithms process more slowly by several orders of magnitude than their symmetric counterparts. Because of this, asymmetric encryption is reserved only for encrypting smaller amounts of data, such as for security keys and tokens and digital signatures.

Encryption Support in WSE

WSE supports the ability to encrypt portions of a SOAP message. Symmetric encryption using a shared secret key, and asymmetric encryption using X.509 certificates are supported. When encrypting a SOAP message using WSE, the entire content of the Body element is encrypted unless explicitly specified otherwise. Examples of both types of encryption are shown, as well as encrypting the entire body of the SOAP message or only part of it.

The WSE runtime implements all WS-Security support in the SecurityInputFilter and SecurityOutputFilter classes. SecurityInputFilter looks for a Security element in an incoming SOAP message and if present, it creates an object that represents any security tokens and encrypted keys, decrypts encrypted elements, and verifies any digital signatures. For an incoming message, any security elements can be accessed through the Security property of the SoapContext object generated for the message. Conversely, SecurityOutputFilter does any encrypting and signing operations and attaches any specified security tokens or encrypted keys to outgoing messages. Security operations such as adding tokens, encrypting, or signing the outgoing message are specified using the SoapContext.Security and SoapContext.ExtendedSecurity properties of the outgoing message. The ExtendedSecurity property is used only when creating security headers required for any intermediaries. The Security property is used when including security information for the ultimate destination. For more information, see Inside the Web Services Enhancements Pipeline.

The WSE provides an object model for programmatically accessing the security properties of the SoapContext object, which allows you to programmatically add and enumerate security tokens and encryption keys, encrypt and decrypt messages, and sign and verify signatures. This object model also leverages the cryptography and XML Encryption support built into the Microsoft® .NET Framework. The WSE object model also extends support for using X.509 certificates to sign and asymmetrically encrypt messages.

Configuring Web Services Enhancements

Even with WSE installed on the ASP.NET Web server, there are a few additional configurations that need to be made to the ASP.NET application in order to use the security support. After creating a new ASP.NET Web service project in Microsoft® Visual Studio .NET, references to the Microsoft.Web.Services.dll assembly needs to be added to the project. You must also add a new SOAP extension to the soapExtensionTypes element. This is done by creating a new add element in the Web.config file for the project as follows:

<configuration>
    <system.web>
      ...
        <webServices>
            <soapExtensionTypes>
                <add type=
                "Microsoft.Web.Services.WebServicesExtension,
                 Microsoft.Web.Services, 
                 Version=1.0.0.0,
                 Culture=neutral,
                 PublicKeyToken=31bf3856ad364e35" 
                priority="1" group="0" />
            </soapExtensionTypes>
        </webServices>
    </system.web>
</configuration>

The value of the type attribute must not include any breaks or extra spaces. This example has extra linefeeds added for readability. If the webServices and soapExtensionTypes elements do not already exist, they must be added to the Web.config file as well. An easier way to configure your WSE-based project is to install the WSE Settings Tool, an add-in to Visual Studio that allows you to easily configure Web service projects that use WSE. There are also a few other security-related configuration settings that must be specified, and I discuss these where appropriate.

When programming security for WSE, you need to add a reference to both the Microsoft.Web.Services assembly (Microsoft.Web.Services.dll) and the .NET Framework System.Security namespace for both the client and service parts of your project, if encryption is used for both the request from the client and the response from the service. For the client portion, you should also use the Add Web Reference tool to generate a Web service proxy for your WSE-based Web service.

Symmetrically Encrypting a SOAP Message

Next, let's take a look at how to use the WSE to encrypt the body of a SOAP message with a symmetric key. The following example is based on a WSE-enabled Web service that returns an XML document containing sensitive data in the body of a SOAP response message. In this case, the client sends a simple Web service request to the service, which returns the XML document encrypted with the Triple DES symmetric encryption algorithm using a shared secret key and initial vector (IV).When the encrypted response is received at the client, the SecurityInputFilter calls a decryption key provider on the client to access the same shared secret on the client needed to decrypt the message body. You must write this decryption key provider and provide a method for synchronizing the shared secret out-of-band. These examples assume that both parties know the secret key and all we need to do is provide the name of the key as a hint as to which key we used to encrypt the message.

Care must be taken when managing, synchronizing, and securing shared secrets between parties. One solution would involve using a secure key distribution mechanism, like Kerberos. Since version 1.0 of WSE does not support Kerberos, you should instead consider negotiating session keys as defined in the WS-SecureConversation specification. For more information on WS-SecureConversation, see Web Services Secure Conversation Language (WS-SecureConversation).

Encrypting Outgoing Messages

Here I describe how to create a Web service that returns an encrypted XML document. The first step is to add using directives for the required namespaces as follows:

using System.Web.Services;
using Microsoft.Web.Services;
using Microsoft.Web.Services.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;

The GetXmlDocument method uses the .NET Framework implementation of the Triple DES algorithm with a shared secret 128-bit key and a 64-bit initialization vector (IV) to generate a symmetric encryption key. This key is assigned a name and added to the SoapContext element for the response message and is used by the SecurityOutputFilter to encrypt the simple XML document that this method ultimately returns to the client. For more information on cryptography support in the .NET Framework, see the Cryptography Overview in the .NET Framework Developer's Guide.

// Method that returns data encrypted with the Triple-DES symmetric algorithm.
[WebMethod (Description="Returns a sensitive XML Document using 
  symmetric encryption", EnableSession=false)]
public XmlDocument GetXmlDocument()
{
  // Create a simple XML document to return.
  XmlDocument myDoc = new XmlDocument();
  myDoc.InnerXml = 
    "<EncryptedResponse>This is sensitive data.</EncryptedResponse>";

  // Get the SoapContext for the outgoing response message.
  SoapContext myContext = HttpSoapContext.ResponseContext;

  // Create a symmetric key for encrypting. Since key is symmetric,
  // this same data must be present on the requesting client.

  // Define the shared 16 byte array representing the 128-bit key
  byte[] keyBytes = {48, 218, 89, 25, 222, 209, 227, 51, 50, 168, 146, 
    188, 250, 166, 5, 206};

  // Define the shared 8 byte (64-bit) array for the initial vector (IV).
  byte[] ivBytes = {16, 143, 111, 77, 233, 137, 12, 72};

  // Create a new instance of the Triple-DES algorithm.
  SymmetricAlgorithm mySymAlg = new TripleDESCryptoServiceProvider();

  // Set the key and IV values. 
  mySymAlg.Key = keyBytes;
  mySymAlg.IV = ivBytes;

  // Create a new WSE symmetric encryption key
  EncryptionKey myKey = new SymmetricEncryptionKey(mySymAlg);

  // Give this key a name.
  KeyInfoName myKeyName = new KeyInfoName();
  myKeyName.Value = "http://example.com/symmetrictestkey";
  myKey.KeyInfo.AddClause(myKeyName);

  // Use the symmetric key to create a new EncryptedData element.
  EncryptedData myEncData = new EncryptedData(myKey);

  // Add the EncryptedData element to SOAP response,
  // which tells the filter to encrypt the body with the specified key. 
  myContext.Security.Elements.Add(myEncData);

  return myDoc;
}

Based on the previous method, the WSE pipeline generates the following response message with the appropriate security header, encryption, and key information elements:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Header>
    <wsu:Timestamp 
      xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility">
      <wsu:Created>2003-02-11T02:07:23Z</wsu:Created>
      <wsu:Expires>2003-02-11T02:12:23Z</wsu:Expires>
    </wsu:Timestamp>
    <wsse:Security soap:mustUnderstand="1" 
      xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/07/secext">
      <xenc:ReferenceList 
        xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
        <xenc:DataReference URI=
          "#EncryptedContent-f50076e3-5aea-435e-8493-5d7860191411" />
      </xenc:ReferenceList>
    </wsse:Security>
  </soap:Header>
  <soap:Body xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility" 
    wsu:Id="Id-d2f22e02-a052-4dcb-8fbc-8591a45b8a9f">
    <xenc:EncryptedData 
      Id="EncryptedContent-f50076e3-5aea-435e-8493-5d7860191411" 
      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#tripledes-cbc" />
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <KeyName>http://example.com/symmetrictestkey</KeyName>
      </KeyInfo>
      <xenc:CipherData>
        <xenc:CipherValue>0T5ThoGg14JmElph...qDJS=</xenc:CipherValue>
      </xenc:CipherData>
    </xenc:EncryptedData>
  </soap:Body>
</soap:Envelope>

Notice that the ReferenceList element contains a reference to the EncryptedData element in the message body, where this element contains the name of the key, the encryption algorithm used, and a cipher-text representation of the data.

Decrypting an Incoming Message

Whether at the client or at the Web server level, WSE implements message decryption in the SecurityInputFilter. Since symmetric encryption requires the encryption key that is derived from the shared secret, you need to create a method that the SecurityInputFilter can call to get this symmetric key, and you can use the key and algorithm information contained in the EncryptedData element to help find the right shared secret and encryption algorithm. This method must be implemented in a class that inherits from Microsoft.Web.Services.Security.IDecryptionKeyProvider. In my sample, the DecryptionKeyProvider.GetDecryptionKey method returns the symmetric key, as follows:

public DecryptionKey GetDecryptionKey(string encAlgorithmUri, 
  KeyInfo keyInfo)
{
  // Recreate the same 16 byte array representing the 128-bit key.
  byte[] keyBytes = {48, 218, 89, 25, 222, 209, 227, 51, 50, 168, 146,
    188, 250, 166, 5, 206};

  // Recreate the 8 byte (64-bit) array for the initial vector (IV)
  byte[] ivBytes = {16, 143, 111, 77, 233, 137, 12, 72};
 
  SymmetricAlgorithm mySymAlg = new TripleDESCryptoServiceProvider();
  mySymAlg.Key = keyBytes;
  mySymAlg.IV = ivBytes;

  // Recreate the symmetric encryption key
  DecryptionKey myKey = new SymmetricDecryptionKey(mySymAlg);

  return myKey;
}

Although I don't use them in my method, WSE also passes the value of the KeyInfo element and the URI string of the encryption algorithm to this method, which I could have used to programmatically decide which shared secrets or encryption algorithm to use in generating the symmetric key.

In order for the SecurityInputFilter to access the GetDecryptionKey method, the following configuration information must be added to the application's configuration file (in this case the App.config file):

<configuration>
  ...
  <microsoft.web.services>
    <security>
      <decryptionKeyProvider 
        type="MyClient Assembly.DecryptionKeyProvider, 
        MyClientAssembly" />
    </security>

The value of the type attribute must not include any breaks or extra spaces. They are only included above for readability. This value can also be set using the WSE Settings Tool add-in. Once the DecryptionKeyProvider class has been added to the client and the WSE security support is configured, the WSE handles decryption automatically so that the client can be programmed just like you would with a regular Web service-consuming client application.

Using X.509 Certificates to Encrypt a SOAP Message

As I mentioned earlier, asymmetric encryption is a costly operation. When transporting large amounts of data, encrypting this data with an asymmetric algorithm becomes impractical from a performance standpoint. WSE gets around this by implementing pseudo-asymmetric encryption. Instead of asymmetrically encrypting the message, WSE use an asymmetric algorithm with a public copy of the recipient's X.509 certificate to encrypt the symmetric key that was actually used to encrypt the message data. When received, the SecurityInputFilter gets the private key associated with the X.509 certificate, decrypts the symmetric key, and uses the decrypted key to decrypt the message body. For this sample to work, an X.509 certificate from a trusted certificate authority (CA) that supports encryption must be present in the Personal certificate store for the current user account on the client machine. The private key for this certificate must also be present in the Personal key store for the local machine account of the server hosting the Web service. In addition, one of the certificates in the CA certificate chain must be present in the client's Trusted store so that WSE knows to trust the incoming X.509 certificate.

Encrypting Outgoing Messages

I have modified the previous GetXmlDocument method to use the X.509-based asymmetric encryption implemented by WSE. To encrypt the response, the FindCertificateBySubjectString method is used to retrieve the public copy of the client's certificate from the Personal store for the local machine account. This certificate is then used to create a new X.509 security token that is added to the security token collection in the SoapContext of the response message. In addition to the namespaces referenced in the symmetric encryption example, you should also add a using directive for the Microsoft.Web.Services.Security.X509 namespace. The GetXmlDocument method is coded as follows:

// Create a simple XML document to return.
XmlDocument myDoc = new XmlDocument();
myDoc.InnerXml = 
  "<EncryptedResponse>This is sensitive data.</EncryptedResponse>";

// Get the SoapContext for the response message.
SoapContext myContext = HttpSoapContext.ResponseContext;

// Open and read the Personal certificate store for 
// the local machine account.
X509CertificateStore myStore = 
  X509CertificateStore.LocalMachineStore(
  X509CertificateStore.MyStore);
myStore.OpenRead();

// Search for all certificates named "My Certificate" and add all
// matching certificates to the certificate collection.
X509CertificateCollection myCerts = 
  myStore.FindCertificateBySubjectString("My Certificate");
X509Certificate myCert = null;

// Find the first certificate in the collection the matches 
// the supplied name, if any.
if (myCerts.Count > 0)
{
  myCert = myCerts[0];
}
// Make sure that we have a certificate that can be 
// used for encryption.
if (myCert == null || !myCert.SupportsDataEncryption)
{
  throw new ApplicationException("Service is not able to 
    encrypt the response");

  return null;
}
else
{
  // Use the valid certificate to create a security token.
  X509SecurityToken myToken = new X509SecurityToken(myCert);

  // Encrypt the message body using this security token.
  // WSE will use this token to encrypt the message body.
  // WSE generates a KeyInfo element used to request the 
  // certificate at the client used to decrypt the message.
  EncryptedData myEncData = new EncryptedData(myToken);

  // Add the encrypted data element to the SoapContext of the 
  // response message.
  myContext.Security.Elements.Add(myEncData);
 
  return myDoc;
}

Based on the previous method, the WSE pipeline generates the following response message with the appropriate security header, encryption, and key information elements:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Header>
    <wsu:Timestamp 
      xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility">
      <wsu:Created>2003-02-11T01:34:01Z</wsu:Created>
      <wsu:Expires>2003-02-11T01:39:01Z</wsu:Expires>
    </wsu:Timestamp>
    <wsse:Security soap:mustUnderstand="1" 
      xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/07/secext">
      <xenc:EncryptedKey 
        Type="http://www.w3.org/2001/04/xmlenc#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="wsse:X509v3">
              YmlKVwXYD8vuGuYliuIYdEAQQPw=
            </wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>UJ64Addf3Fd59XsaQ=…</xenc:CipherValue>
        </xenc:CipherData>
        <xenc:ReferenceList>
          <xenc:DataReference URI=
            "#EncryptedContent-608eef8b-4104-4469-95b6-7cb4703cfa03" />
        </xenc:ReferenceList>
      </xenc:EncryptedKey>
    </wsse:Security>
  </soap:Header>
  <soap:Body xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility" 
    wsu:Id="Id-70179c5b-4975-4932-9ecd-a58feb34b0d3">
    <xenc:EncryptedData 
      Id="EncryptedContent-608eef8b-4104-4469-95b6-7cb4703cfa03" 
      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#tripledes-cbc" />
      <xenc:CipherData>
        <xenc:CipherValue>
          4o1b4befwBJu6tzuaygfrAaX0UGtaYKcw2klIbuZPjLi...z8i2ypHN4+w==
        </xenc:CipherValue>
      </xenc:CipherData>
    </xenc:EncryptedData>
  </soap:Body>
</soap:Envelope>

Notice in this encrypted message that the asymmetrically encrypted EncryptedKey element contains the symmetric key that was used to encrypt the message body. The ReferenceList element references the Id value of the EncryptedData element in the message body. Although I did not do so in my sample, it would probably be a good idea to sign this message so that the recipient can verify the sender. For information on signing messages using WSE, see WS-Security Authentication and Digital Signatures with Web Services Enhancements.

Decrypting an Incoming Message

When receiving an incoming message that has been encrypted by a X.509 certificate, the SoapInputFilter automatically attempts to decrypt the message using the private key in the user's key store that is related to the presented X.509 certificate. Of course, this requires another configuration piece that tells the WSE runtime where to find this certificate. This information is specified in the Security element in the application configuration file, which for this example is App.config on the client. For X.509 encryption, you need to add a child x509 element as follows:

<x509
  storeLocation="CurrentUser"
  verifyTrust="true"
  allowTestRoot="false" />

In my sample, I set storeLocation to CurrentUser since the certificate is in the current user's certificate store, and since I am using a certificate from a trusted root CA, I also set verifyTrust to true. These values can also be set using the WSE Settings Tool add-in. With this information, the WSE can get the private key for the certificate in the message and use this to decrypt the symmetric session key, which is in turn used to decrypt the message body.

Selecting the Message Elements to Encrypt

While the entire message body is encrypted by default, WSE can be used to encrypt specific elements within the SOAP message; the only exception being that the elements in the Security header element cannot be encrypted. You can encrypt nested elements, provided that the EncryptedData elements are added to the Secuity.Elements collection for SoapContext object in the order of increasing overlap, with the EncryptedData element for the body being added last.

In this sample service, I modified the X.509 version of the GetXmlDocument method to use a single X.509-based security token to digitally encrypt both the EncryptedSubResponse and its EncryptedResponse parent element of the following XML document that is returned:

<Response>
  <NotEncrypted>
    This part of the response does not need encryption
  </NotEncrypted>
  <EncryptedResponse>
    <EncryptedSubResponse>
      This is sensitive data.
    </EncryptedSubResponse>
  </EncryptedResponse>
</Response>

In order to encrypt an element, it needs to have the wsu:Id attribute programmatically assigned to it so that the reference is added to the element when the XML is serialized, where wsu is defined as:

xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility

To do this, I added this XML to a new XML Document and added the Id attributes using the Microsoft XML Document Object Model (DOM) support in the .NET Framework, which requires adding System.Xml assembly to the project references plus the following using directives:

using System.Xml;
using System.Xml.Serialization;

Since I was adding multiple Id attributes to nested elements, I started with the EncryptedSubResponse element and looped through to its parent EncryptedResponse, as follows:

// Create a string array containing the two Id values. 
string [] myId = {"Id:" + Guid.NewGuid(),"Id:" + Guid.NewGuid()};

// Create an XML Document for the returned XML.
XmlDocument myDoc = new XmlDocument();
myDoc.LoadXml("<Response>" +
  "<NotEncrypted>This part of the response does not need encryption" +
  "</NotEncrypted>" +
  "<EncryptedResponse>" +
  "<EncryptedSubResponse>" +
  "This is sensitive data. " +
  "</EncryptedSubResponse>" +
  "</EncryptedResponse>" +
  "</Response>");

// Get the EncryptedSubResponse node
XmlNode    = myDoc.FirstChild.LastChild.FirstChild;

// Loop upward through the elements adding the two Id attributes.
// Moving upward ensures that the inner-most element will be encrypted
// first, otherwise we get an exception.
for (int i=0;i<myId.Length;i++)
{
  //Create the new Id attribute.
  string wsu = "https://schemas.xmlsoap.org/ws/2002/07/utility";
  XmlNode myAttr = myDoc.CreateNode(XmlNodeType.Attribute, "wsu",
    "Id", wsu);
  myAttr.Value = myId[i];

  //Add the attribute to the document.
  root.Attributes.SetNamedItem(myAttr);
  root = root.ParentNode; // move up to the parent
}

Assuming that I already have obtained the Security Token from the X.509 certificate using my previous logic, I added these references to the EncryptedData elements, as follows:

// Loop through the Id values to add new EncryptedData elements.
for (int i=0;i<myId.Length;i++)
{
  // Create a new header. The # is prepended to the Id value 
  // to make this an appropriate relative URI that can be 
  // referenced from the header.
  EncryptedData myEncHeader = new EncryptedData(myToken, "#"+myId[i]);

  // Add the new header to the collection.
  myContext.Security.Elements.Add(myEncHeader);
}   

// Return the encrypted data
return myDoc;

This results in the following encrypted response message being serialized by the WSE runtime:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Header>
    <wsu:Timestamp 
      xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility">
      <wsu:Created>2003-02-11T20:21:52Z</wsu:Created>
      <wsu:Expires>2003-02-11T20:26:52Z</wsu:Expires>
    </wsu:Timestamp>
    <wsse:Security soap:mustUnderstand="1" 
      xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/07/secext">
      <xenc:EncryptedKey 
        Type="http://www.w3.org/2001/04/xmlenc#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="wsse:X509v3">
              YmlKVwXYD8vuGuYliuIOXOY7ZYN9PwHbfAhCiYOV0aYdEAQQPw=
            </wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>
            UyKGBEXdY8lYSzqgdgxOXOY7ZYN9PwHbfAhCiYOV0...bwRnWk=
          </xenc:CipherValue>
        </xenc:CipherData>
        <xenc:ReferenceList>
          <xenc:DataReference URI=
            "#EncryptedContent-cf014249-0e2a-4f8b-9002-13a7de916be0" />
        </xenc:ReferenceList>
      </xenc:EncryptedKey>
      <xenc:EncryptedKey 
        Type="http://www.w3.org/2001/04/xmlenc#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="wsse:X509v3">
              YmlKVwXYD8vuGuYliuIYdEAQQPw=
            </wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>
            In8Kf1cIdiJJJXCLZ+... wMqBEevXmzk=
          </xenc:CipherValue>
        </xenc:CipherData>
        <xenc:ReferenceList>
          <xenc:DataReference URI=
            "#EncryptedContent-0744279a-02bf-4ad1-998e-622208eded0e" />
        </xenc:ReferenceList>
      </xenc:EncryptedKey>
    </wsse:Security>
  </soap:Header>
  <soap:Body>
    <GetXmlDocumentResponse xmlns="http://example.com/dime/">
      <GetXmlDocumentResult>
        <Response>
          <NotEncrypted>
            This part of the response does not need encryption
          </NotEncrypted>
          <EncryptedResponse 
           wsu:Id="Id:e5e8d792-abe7-4476-91d0-856fbdf4a958" 
           xmlns:wsu="https://schemas.xmlsoap.org/ws/2002/07/utility">
            <xenc:EncryptedData 
              Id=
              "EncryptedContent-cf014249-0e2a-4f8b-9002-13a7de916be0" 
              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#tripledes-cbc" />
              <xenc:CipherData>
                <xenc:CipherValue>
                  2MNHCkGVH/5jb0pF4pCh3u2VaUKsWSA...AfEvJZT=
                </xenc:CipherValue>
              </xenc:CipherData>
            </xenc:EncryptedData>
          </EncryptedResponse>
        </Response>
      </GetXmlDocumentResult>
    </GetXmlDocumentResponse>
  </soap:Body>
</soap:Envelope>

Notice in this encrypted message that there is a single BinarySecurityToken element representing the X.509 certificate, but two separate EncryptedKey elements, one for each EncryptedData elements that was added to the SoapContext. You can only see the EncryptedData element for the outermost encrypted element (EncryptedResponse), since the EncryptedData element representing EncryptedSub was converted to cipher text when EncryptedResponse was encrypted. When this message is received at the client, the SecurityInputFilter uses the information from the Token to get the private key that is used to decrypt both EncryptedKey elements. This frees the symmetric encryption keys to first decrypt the Encrypted element and then the EncryptedSubResponse element.

Limitations and Interoperability Issues

One of the limitations of the WSE implementation of WS-Security is that there is no built-in mechanism for encrypting attached items, as specified in WS-Attachments. When you tell WSE to encrypt a SOAP message, the SOAP message passes through the WSE SecurityOutputFilter where parts of the message are encrypted. Only after passing through the WSE filter pipeline does the SOAP message and any attachments get encapsulated using Direct Internet Message Encapsulation (DIME). This means that the attachments themselves are not secured and can be read by any DIME parser. If you need to secure the attachments in DIME messages, you should use one of the .NET Framework-supported encryption mechanisms provided by the System.Security.Cryptography namespace. This means that you need to do your own encryption and decryption of the attachments outside of the WSE pipeline. Aside from this, the only other major restrictions seem to be the lack on any Kerberos support and the inability to encrypt anything in the Security header.

Conclusion

The WS-Security specification provides a solid first-step towards a much needed security infrastructure for interoperable Web services in the enterprise. WSE 1.0 SP 1 supports both symmetric and X.509-based encryption, and between these two the X.509-based encryption provides the flexibility of using asymmetric encryption with the efficiency of a symmetric algorithm. While it lacks support for encrypting external message attachments and Kerberos, WSE provides a good starting point for securing your Web service.