Export (0) Print
Expand All
15 out of 34 rated this helpful - Rate this topic

Encrypting SOAP Messages

 

Rob Howard
Microsoft Corporation

September 27, 2001

XML Web Services are being hailed by the industry as the enabler for freeing information from the confines of HTML. Using SOAP, a protocol submitted to the W3C, data can be encoded in XML and transmitted using any number of Internet protocols, HTTP being the most common. So long as both the sender and the receiver can agree upon the message format—that is, the protocol that SOAP defines—information can easily be exchanged in a platform-independent manner.

Microsoft® is taking a leadership role in ensuring the success of XML Web Services, not only by stating its vision, but also by implementing technologies and products. And of course, one of the most anticipated products supporting XML Web Services is Microsoft® ASP.NET.

Building XML Web Services with ASP.NET

ASP.NET makes building Web Services simple. Just as ASP revolutionized Web development for early Web developers—fond memories of PERL/CGI inserted here—ASP.NET will revolutionize building Web applications that emit HTML or XML (conforming to the SOAP protocol).

As mentioned, building XML Web Services with ASP.NET is simple. As developers, we author a class with method definitions marked by custom attributes (custom attributes are a unique feature to .NET) all existing within a file using the extension, .asmx. For example, below is a simple Web Service:

<%@ WebService Language="C#" Class="SimpleMath" %>

using System.Web.Services;

public class SimpleMath {

  [WebMethod]
  public int Add(int a, int b) {
    return a+b;
  }
}

A SOAP message can be sent to this service:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <Add xmlns="http://tempuri.org/" />
      <a>5</a>
      <b>8</b>
    </Add>
  </soap:Body>
</soap:Envelope>

The ASP.NET XML Web Service will then respond accordingly:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <AddResponse xmlns="http://tempuri.org/" />
      <AddResult>13</AddResult>
    </AddResponse>
  </soap:Body>
</soap:Envelope>

The benefit to the developer is obvious. There is no need to understand XML, HTTP, SOAP or any of the other protocols used to build XML Web Services. Instead, the developer focuses on building the solution.

What About Security?

One of the key concerns for developers is security. The above examples demonstrate what is sent on 'the wire' between two XML Web Services. As you'll note, the exchange is done in plain text. Because the data is sent in plain text and is routed across the Internet to reach its final destination, anyone can potentially view the message exchange. In the case of the example above, this may not be an issue. However, what if the exchange consisted of a request to a bank, and the response contained a list of account numbers and balances, or perhaps a request that returned something even more interesting, such as a credit card number? Obviously this is not something we would want anyone to view!

Supporting Encryption

In the case where we have data to exchange and we don't want prying eyes to view it, we must secure the communication through encryption. The SOAP specification does not define encryption for XML Web Services. Instead, this is left up to the implementer of the SOAP protocol, and in many cases the suggested security measure is HTTPS (secure HTTP).

SOAP over HTTPS is secure. The entire HTTP message, including both the headers and the body of the HTTP message is encrypted using public asymmetric encryption algorithms. However, this type of encryption places a dependency on the transport protocol: HTTP, whereas SOAP is a transport-independent protocol. A SOAP message can be sent using HTTP, SMTP, TCP, UDP, and so on.. A SOAP message can also be routed, meaning a server can receive a message, determine that it is not the final destination, and route the message elsewhere. During the routing, different transport protocols can be used, for example, HTTP -> UDP -> SMTP -> HTTP. Therefore a dependency on the transport protocol for security has an intrinsic problem: how much trust can be placed on the routing servers, as the routing server must be able to decrypt the message to read it, and then re-encrypt for another transport protocol? Other questions that arise are:

  • Does the transport protocol support secure communication?
  • What is the cost of encrypting all the data versus part of the data?

Although the above paragraph is completely valid, I should note that in many cases HTTPS is the recommended solution for securing SOAP message exchanges because nearly all XML Web Services today use HTTP for the transport protocol, and very few XML Web Services, currently support message routing. However, it is possible to easily build ASP.NET Web Services that support encryption of the SOAP message. The encryption can be done using public encryption algorithms, which take the dependency off the transport protocol, and allows for only select parts of the message to be encrypted. For example, the solution below uses single pass Data Encryption Standard (DES) encryption (a public symmetric encryption algorithm in which both parties must share a common key), and only the body of the message is encrypted (allowing routing servers to read the headers, but not the body).

SOAP Extensions

The encryption support that we'll build for ASP.NET Web Services will come in the form of a SOAP extension. A SOAP extension will allow the developer implementing the ASP.NET Web Service to simply add another attribute to the method (along with the WebMethod attribute), and have the message encrypted or decrypted. Below is a sample of what the code for our ASP.NET Web Service looks like with this new extension:

<%@ WebService Language="C#" Class="CreditCardService" %>

using System.Web.Services;

public class CreditCardService {

  [WebMethod]
  [EncryptionExtension(Encrypt=EncryptMode.Response)]
  public string GetCreditCardNumber() {
    return "MC: 4111-1111-1111-1111";
  }
}

We are using a fairly simplistic example here, but you'll note that we've simply added a single custom attribute to the GetCreditCardNumber method:

  [EncryptionExtension(Encrypt=EncryptMode.Response)]

This is the custom SOAP extension attribute, the source of which we'll examine momentarily. At the time of writing this article, the implementation only works for Encrypt=EncryptMode.Response and Decrypt=DecryptMode=Request—and of course the EncryptMode.None and Decrypt.None values.

Requests can be sent to the Web Service in plain text, and responses will be encrypted. Below is what a typical exchange might look like. First comes the request:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <GetCreditCardNumber xmlns="http://tempuri.org/" />
  </soap:Body>
</soap:Envelope>

Next comes the response:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <GetCreditCardNumber xmlns="http://tempuri.org/">
      <GetCreditCardNumberResult>83 151 243 32 53 95 86 13 190 134 188 241 
198 209 72 114 122 38 180 34 194 138 16 97 221 195 239 86 26 152 94 27
      </GetCreditCardNumberResult>
    </GetCreditCardNumber>
  </soap:Body>
</soap:Envelope>

As you can clearly see, the response is still a valid SOAP message. However, rather than transmitting the credit card number in plain text, an encrypted array of bytes is exchanged.

Note there are other security issues here. For instance, the method should probably not be called GetCreditCardNumber, as this could tip off a skilled hacker. Nevertheless, the point is that the data sent by the XML Web Service is still sent in a valid SOAP message that can be routed over HTTP, SMTP, or any other transport protocol—without compromising the security of the message, as only the end-client possessing the correct key can decrypt the message. In the case of .NET clients, this is simply a matter of adding the attribute to the proxy class generated by either wsdl.exe or Visual Studio .NET:

...
[SoapDocumentMethodAttribute("http://tempuri.org/GetCreditCardNumber", 
                             Use= SoapBindingUse.Literal, 
                             ParameterStyle= SoapParameterStyle.Wrapped)]
[EncryptionExtension(Decrypt=DecryptMode.Request)]
public string GetCreditCardNumber() {
    object[] results = this.Invoke("GetCreditCardNumber", new object[0]);
    return ((string)(results[0]));
}
...

In both of the above cases, we're making use of a SOAP extension. SOAP Extensions are classes we develop—they derive from SoapExtension—that allow us to interact at a low-level with the SOAP serialization process. In the diagram below, the points with the red asterisks represent the opportunities for interaction through SOAP extensions:

Figure 1. SOAP serialization and deserialization

Let's look at the code behind the SOAP EncryptionExtension. Note, we're not going to look at all of the source, only small sections. For the complete source, please see http://www.gotdotnet.com/team/rhoward.

The EncryptionExtension class derives from SoapExtension; found in the System.Web.Services.Protocols namespace. Classes that inherit from SoapExtension must provide an implementation for several methods and properties. The most important one is the ProcessMessage(SoapMessage message) method seen below:

public class EncryptionExtension : SoapExtension { 
   
  public override void ProcessMessage(SoapMessage message) {
    switch (message.Stage) {

      case SoapMessageStage.BeforeSerialize:
        break;

      case SoapMessageStage.AfterSerialize:
        Encrypt();
        break;

      case SoapMessageStage.BeforeDeserialize:
        Decrypt();
        break;

      case SoapMessageStage.AfterDeserialize:
        break;

      default:
        throw new Exception("invalid stage");
      }
   }
...
}

It is within this overridden method that we have the opportunity to interact with the serialization of the SOAP message in the four stages diagramed above. In the case of our EncryptionExtension, we participate only in the AfterSerialize and BeforeDeserialize stages for encrypting and decrypting. If, for example, a request comes into to our GetCreditCardNumber Web Service after the response is serialized, our EncryptionExtension will call Encrypt() from ProcessMessage ():

  private void Encrypt() {
    newStream.Position = 0;

    if ((encryptMode == EncryptMode.Request) || 
        (encryptMode == EncryptMode.Response)) {
      newStream = EncryptSoap(newStream);
    }

    Copy(newStream, oldStream);
  }

Within the Encrypt() method we check to see if the EncryptMode property of the EncryptionExtension attribute is set. If it is set to EncryptMode.Request or EncryptMode.Response an internal method EncryptSoap() is called. EncryptSoap() accepts the stream representing the SOAP message, and returns a new stream with the encrypted values:

  public MemoryStream EncryptSoap(Stream streamToEncrypt) {
    streamToEncrypt.Position = 0;
    XmlTextReader reader = new XmlTextReader(streamToEncrypt);
    XmlDocument dom = new XmlDocument();
    dom.Load(reader);

    XmlNamespaceManager nsmgr = new XmlNamespaceManager(dom.NameTable);
    nsmgr.AddNamespace("soap", 
                       "http://schemas.xmlsoap.org/soap/envelope/");
    XmlNode node = dom.SelectSingleNode("//soap:Body", nsmgr);
    node = node.FirstChild.FirstChild;

    byte[] outData = Encrypt(node.InnerText);

    StringBuilder s = new StringBuilder();

    for(int i=0; i<outData.Length; i++) {
      if(i==(outData.Length-1))
        s.Append(outData[i]);
      else
        s.Append(outData[i] + " ");
    }

    node.InnerText = s.ToString();

    MemoryStream ms = new MemoryStream();
    dom.Save(ms);
    ms.Position = 0;

    return ms;
  }

Within EncryptSoap() it is hardwired to encrypt the first node in the soap:Body. The appropriate XmlNode is selected, and the text within the node is passed to another Encrypt() method, which accepts a string and returns an array of bytes. It is this final Encrypt() method that uses the .NET DES classes to encrypt the string and return the byte array of the encrypted value:

  private byte[] Encrypt(string stringToEncrypt) {
    DESCryptoServiceProvider des = new DESCryptoServiceProvider();

    byte[] inputByteArray = Encoding.UTF8.GetBytes(stringToEncrypt);

    MemoryStream ms = new MemoryStream();
    CryptoStream cs = new CryptoStream(ms, 
                                       des.CreateEncryptor( key, IV ), 
                                       CryptoStreamMode.Write);

    cs.Write(inputByteArray, 0, inputByteArray.Length);
    cs.FlushFinalBlock();

    return ms.ToArray();
  }

Many people have asked me if this solution is .NET specific; it most definitely is not. It does, however, require that both parties are aware that DES encryption is used, and it does require that a non-.NET caller use the DES algorithm (note, DES is a publicly available algorithm).

As for improvements to the above example, there could be many:

  • Support asymmetric encryption: the caller uses the server's public key to encrypt and the server uses the client's key to encrypt a response.
  • Currently the example only supports a plain-text request and an encrypted response.
  • Support encryption of SOAP headers for sending authentication information and encryption of those details only.

Summary

As you can see, ASP.NET Web Services are very, very powerful. Most developers are wholly unaware of the power of SOAP Extensions and how they can be used to do all kinds of new and interesting things for Web Services. For example, because of the functionality provided by ProcessMessage(), it's very easy to build into ASP.NET Web Services options like XML compression, routing, encryption, logging, payment handling, and so on. Using a solution such as this, it is definitely possible to build XML Web Services that can securely communicate over the Internet while still using SOAP.

Rob Howard is a program manager for ASP.NET on the .NET Frameworks team. He spends whatever spare time he has either with his family or fly fishing in Eastern Washington.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.