Secure Communication


Many of the examples in this chapter have involved passing sensitive information over the Internet, such as unencrypted usernames and passwords. Although some of the authentication methods, such as Digest authentication, attempt to accommodate this, there is no substitute for encryption. By ensuring that nobody can listen in on the exchange of information you make with a Web service, you can ensure security. This applies not only to the passwords you send to a Web service but also to the information you receive in return, which might be just as sensitive.

The simplest means of achieving security is to use Secure Sockets Layer (SSL) encryption, which means using the HTTPS protocol. Alternatively, you can implement your own encryption scheme. We’ll look at both of these approaches.

SSL

To use SSL encryption, your Web server must be configured with a server certificate. A certificate is essentially a combination of a public key that clients can use to encrypt information and a private key for the Web server to decrypt information. (The public key cannot be used to decrypt data.) Server certificates are also digitally signed by a certification authority that identifies where they come from. It is possible to generate your own certificates, but for customer confidence it is usually better to obtain one from a third party such as VeriSign. Users won’t receive a warning message if they use a certificate from a known authority because the digital signatures are (for all practical purposes) universally recognized.

We won’t go through the details of obtaining and installing a server certificate— instructions are widely available. Instead, we’ll assume that you have installed a server certificate, and we’ll look at setting up secure communication with a Web service.

With a server certificate in place, we can access any of our Web services over SSL simply by replacing the http: part of the URL used to reference them with https:. However, in some cases (such as the Logon.asmx Web service we used earlier in our custom authentication example) we need to restrict access to only SSL. To do this, we just check a box in IIS Manager for the file or directory we want to restrict access to, as shown in Figure 11-6.

click to expand
Figure 11-6: Configuring a Web service to require SSL access

Note

If you have a server certificate installed, the Secure Communications dialog box allows you to configure client certificate mapping and to specify whether client certificates will be used. We’ll look at this type of authentication in more depth in Chapter 15.

Using SSL is an effective way to achieve secure communication, but this approach isn’t perfect. Perhaps the biggest problem is with performance—especially when you’re dealing with large amounts of data. Asymmetric encryption, although extremely secure, isn’t the speediest thing in the world. If performance isn’t an issue, go ahead and use SSL for all your Web service communications; otherwise, think carefully before you do.

One final point: Web service code is capable of determining whether HTTPS is being used, by examining the Context.Request.IsSecureConnection property. This is a Boolean value that is true if an SSL connection has been made to the Web service, and false otherwise. Typically, we use this property as follows:

[WebMethod] public string GetToken(string userName, string password) {     if (!this.Context.Request.IsSecureConnection)         throw new System.Net.WebException("Secure connection required.");      

Of course, by this point the client will already have exposed its security details, but at least it will go no further!

Custom Cryptography

There are many ways around the performance problems of SSL. One solution is to use SSL to exchange a shared symmetric key and use plain HTTP to exchange messages encrypted using that key. Another solution is to encrypt only the sensitive parts of the SOAP messages that are sent back and forth.

Next we’ll look at one way of doing this—by creating a SOAP extension that allows the encryption and decryption of portions of the SOAP message by way of XPath selection of sensitive elements.

Encrypting Portions of a SOAP Message

The code for this SOAP extension is in the WebServiceEncryptionExtension project in the sample code. As with all SOAP extensions, this one defines an attribute for applying to Web methods and defines the SoapExtension-derived class for carrying out the extension’s actions.

The code for the attribute, EncryptMessageAttribute, is as follows:

[AttributeUsage(AttributeTargets.Method)] public class EncryptMessageAttribute : SoapExtensionAttribute {     private string[] xPathExpressionArray = new string[] {         "/soap:Envelope"};     public override Type ExtensionType     {        get        {            return typeof(EncryptMessage);        }     }     public override int Priority     {         get         {             return 0;         }         set         {         }     }     public string XPathEncryption     {         get         {             StringBuilder sb = new StringBuilder();             foreach (string query in xPathExpressionArray)             {                 sb.Append(query);                 sb.Append(',');             }             return sb.ToString(0, sb.Length - 1);         }         set         {             xPathExpressionArray = value.Split(',');         }     }     internal string[] XPathExpressionArray     {         get         {             return xPathExpressionArray;         }     } }

The only nonstandard code here is for the public XPathEncryption property, which you can use to control the behavior of the encryption extension. This is set via a comma- separated list of values but is stored as an array of strings, and it is made available internally (that is, to the SOAP extension class) in this format. By default, the extension encrypts everything contained in <soap:Envelope>, although this is more than you’ll need in most cases.

Next we have the extension class itself, EncryptMessage; its private members are as follows:

public class EncryptMessage : SoapExtension {     private Stream oldStream;     private MemoryStream newStream;     private byte[] key = {1, 2, 3, 4, 5, 6, 7, 8};     private byte[] iv = {1, 2, 3, 4, 5, 6, 7, 8};     private string[] xPathExpressionArray;

The oldStream and newStream objects are used in the ChainStream method, which follows the standard syntax for stream manipulation in SOAP extensions:

    public override Stream ChainStream(Stream stream)     {         oldStream = stream;         newStream = new MemoryStream();         return newStream;     }

The key and iv members are used as a key for encryption purposes. (This class obviously doesn’t implement a particularly strong encryption method—only an easily- guessed 64-bit key is used—but you can easily modify that sort of thing in production code.) xPathExpressionArray is extracted from the attribute we examined previously in the initialization code for the class.

    public override object GetInitializer(Type serviceType)     {         return typeof(EncryptMessage);     }     public override object GetInitializer(LogicalMethodInfo methodInfo,         SoapExtensionAttribute attribute)     {         return attribute;     }     public override void Initialize(object initializer)     {         EncryptMessageAttribute attribute =           initializer as EncryptMessageAttribute;         xPathExpressionArray = attribute.XPathExpressionArray;     }

Next we have ProcessMessage, which calls either Encrypt or Decrypt, depending on the message stage:

    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 ArgumentException("Invalid SOAP Message Stage.");         }     }

Encrypt starts by loading the formatted SOAP document from the stream newStream into an XML document:

    public void Encrypt()     {         // get newStream into XML document         newStream.Position = 0;         XmlTextReader reader = new XmlTextReader(newStream);         XmlDocument doc = new XmlDocument();         doc.Load(reader);
Note

The code for this chapter includes a simple logging facility in Encrypt and Decrypt, which you can uncomment if you want to see the results of this encryption in action.

Next we iterate through each of the nodes selected by each of the XPath expressions in xPathExpressionArray, encrypting the data using EncryptString, which we’ll look at shortly:

        // manipulate XML         XmlNamespaceManager nsMan = new XmlNamespaceManager(doc.NameTable);         nsMan.AddNamespace("soap",             "http://schemas.xmlsoap.org/soap/envelope/");         foreach (string xPathQuery in xPathExpressionArray)         {             XmlNodeList nodesToEncrypt =                  doc.SelectNodes(xPathQuery, nsMan);             foreach (XmlNode nodeToEncrypt in nodesToEncrypt)             {                 nodeToEncrypt.InnerXml =                      EncryptString(nodeToEncrypt.InnerXml);             }         }

When the XML is encrypted, we copy the XML data back into newStream and then copy newStream to oldStream to send the encrypted data:

        // put manipulated XML into newStream         newStream.Position = 0;         XmlTextWriter writer = new XmlTextWriter(newStream, Encoding.UTF8);         doc.Save(writer);                newStream.Position = 0;         Copy(newStream, oldStream);     }

The Copy method called here simply copies data from one stream to another as follows:

    private void Copy(Stream from, Stream to)      {         TextReader reader = new StreamReader(from);         TextWriter writer = new StreamWriter(to);         writer.Write(reader.ReadToEnd());         writer.Flush();     }

Decrypt contains similar code to Encrypt. We start by loading the encrypted data into a new MemoryStream called tempStream and converting this data into XML:

    public void Decrypt()     {         // copy original stream into tempStream         MemoryStream tempStream = new MemoryStream();         Copy(oldStream, tempStream);         // get tempStream into XML document         tempStream.Position = 0;         XmlTextReader reader = new XmlTextReader(tempStream);         XmlDocument doc = new XmlDocument();         doc.Load(reader);

Next we use DecryptString to get back the original XML data:

        // manipulate XML         XmlNamespaceManager nsMan = new XmlNamespaceManager(doc.NameTable);         nsMan.AddNamespace("soap",             "http://schemas.xmlsoap.org/soap/envelope/");         foreach (string xPathQuery in xPathExpressionArray)         {             XmlNodeList nodesToEncrypt =                  doc.SelectNodes(xPathQuery, nsMan);             foreach (XmlNode nodeToEncrypt in nodesToEncrypt)             {                 nodeToEncrypt.InnerXml =                      DecryptString(nodeToEncrypt.InnerXml);             }         }

Finally we fill tempStream with the unencrypted XML data and then copy the data into newStream so it is ready for processing by the Web service:

        // put manipulated XML into tempStream         tempStream = new MemoryStream();         XmlTextWriter writer =              new XmlTextWriter(tempStream, Encoding.UTF8);         doc.Save(writer);         // copy manipulated stream to newStream         tempStream.Position = 0;         Copy(tempStream, newStream);         newStream.Position = 0;     }

The next two methods, EncryptString and DecryptString, can be replaced to use any type of encryption you like. In this example, we encrypt and decrypt using a 64-bit DES scheme (hence the key and iv members we examined earlier). The original string is encrypted into a byte array, the byte array is serialized into an XML document, and the encoded string is extracted from this XML document:

    private string EncryptString(string sourceString)     {         // get memory stream for encrypted data         MemoryStream encryptedStream = new MemoryStream();                // get encryptor and encryption stream         DESCryptoServiceProvider encryptor =              new DESCryptoServiceProvider();         CryptoStream encryptionStream =             new CryptoStream(encryptedStream,                  encryptor.CreateEncryptor(key, iv),                  CryptoStreamMode.Write);                // encrypt bytes from newStream         byte[] sourceBytes = Encoding.UTF8.GetBytes(sourceString);         encryptionStream.Write(sourceBytes, 0, sourceBytes.Length);         encryptionStream.FlushFinalBlock();         // Serialize         XmlSerializer serializer = new XmlSerializer(typeof(byte[]));         StringBuilder sb = new StringBuilder();         TextWriter writer = new StringWriter(sb);         serializer.Serialize(writer, encryptedStream.ToArray());         writer.Flush();         // Extract relevant XML         XmlDocument doc = new XmlDocument();         doc.LoadXml(sb.ToString());                return doc.DocumentElement.InnerXml;     }

DecryptString reverses this process. It starts by wrapping the encoded string in the XML required for it to be deserialized, the data is then deserialized into a byte array, and finally this byte array is interpreted using UTF-8 encoding to obtain the decrypted data:

    private string DecryptString(string sourceString)     {         // Deserialize         sourceString =             "<?xml version=\"1.0\" encoding=\"utf-16\"?><base64Binary>" +             sourceString + "</base64Binary>";         XmlSerializer deSerializer = new XmlSerializer(typeof(byte[]));         TextReader reader = new StringReader(sourceString);         byte[] sourceBytes = deSerializer.Deserialize(reader) as byte[];         // get new memory stream for decrypted data         MemoryStream tempStream = new MemoryStream();                // get  decryptor and decryption stream         DESCryptoServiceProvider decryptor = new DESCryptoServiceProvider();         CryptoStream decryptionStream =             new CryptoStream(tempStream,                  decryptor.CreateDecryptor(key, iv),                  CryptoStreamMode.Write);                // decrypt data         decryptionStream.Write(sourceBytes, 0, sourceBytes.Length);         decryptionStream.FlushFinalBlock();         return Encoding.UTF8.GetString(tempStream.ToArray());     } }

Now all we need to do is to test this code. The code samples include a Web service called EncryptedService.asmx, which uses the following Web method:

    [WebMethod]     [EncryptMessage(XPathEncryption="//soap:Body/*/*")]     public string GetBankAccountPassword()     {         return "Cerberus";     }

This simple Web method is marked with the EncryptMessage attribute, which uses the XPath expression “//soap:Body/*/*” to specify that children of children of the <soap:Body> element should be encrypted, which in effect means that the <GetBankAccountPasswordResult> element of the response SOAP message will be encrypted.

Tip

Try replacing the XPath expression “//soap:Body/*/*” with other search strings or even omitting the parameter entirely to encrypt the entire contents of <soap:Envelope>.

Next comes the only really tricky part of this example—this SOAP extension must be applied to both the Web service and the client (specifically, to the proxy class that the client uses to call the Web method). This is tricky because we are likely to be dealing with autogenerated code for this proxy, which is re-created every time we use the Add Web Reference or wsdl.exe tools.

We need to modify the proxy code in the hidden file Reference.cs as follows:

using System.Diagnostics; using System.Xml.Serialization; using System; using System.Web.Services.Protocols; using System.ComponentModel; using System.Web.Services; using NotAShop.WSCR.Chapter11.WebServiceEncryptionExtension; [...] public class EncryptedService :      System.Web.Services.Protocols.SoapHttpClientProtocol  {          [...]     [EncryptMessage(XPathEncryption="//soap:Body/*/*")]     public string GetBankAccountPassword()      {         object[] results = this.Invoke("GetBankAccountPassword",             new object[0]);         return ((string)(results[0]));     }

Now the client code to call the Web method is simply this:

EncryptedService service = new EncryptedService(); Console.WriteLine(service.GetBankAccountPassword());

Tip

Stepping through the SOAP extension code on the server side can be a little tricky. Try putting a breakpoint in the second line of the client code shown above, and before resuming execution attach to the aspnet_wp.exe process. That way, you should be able to step through into the more interesting code.

Concerns with Custom Cryptography

The biggest problem with custom cryptography is accommodating non-.NET clients. We wrapped our cryptographic code inside a SOAP extension that is really only usable by .NET code. The key required for decryption isn’t available externally, so clients might have a problem. One way around this is to exchange keys using a different secure method, such as SSL, and to write custom client-side code to consume the Web service.

However, we still have a problem: the XML passed between server and client might not be a valid SOAP message, and even if it is, it might not match up with the WSDL description for the Web service. This might confuse clients who expect such a message or confuse intermediate nodes that are responsible for processing SOAP headers.

Despite these problems, though, custom cryptography can be useful in corporate systems—especially when you need to pass large amounts of data—because not everything needs to be encrypted. It can also be a great help when you pass security details in a SOAP header because we can use the attribute with an XPath expression that encodes just that portion of the SOAP message.




Programming Microsoft. NET XML Web Services
Programming MicrosoftВ® .NET XML Web Services (Pro-Developer)
ISBN: 0735619123
EAN: 2147483647
Year: 2005
Pages: 172

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net