Message Signing and Encrypting


As you know, using authentication really provides no security for the message transfer between the client and the server. You can move to a secure transport protocol (such as HTTPS) in many cases, but you might not want to go to the expense of purchasing an SSL certificate, or you might actually use a transport protocol that cannot be secured at the transport layer (such as SMTP). In these cases, you have to look at the two other options: message signing and encrypting.

You can sign a message using username and password credentials. However, as we pointed out earlier, the relationship between the username and the password is relatively trivial—it would be easy to intercept the message and retrieve the password used to sign the message, thereby destroying any security. Because you’re unlikely to ever use username and password credentials to sign a message, we won’t implement this approach in our example—the WSE documentation has full details of how to sign the message using these credentials. However, we will allow the signing of messages using X509 certificates.

If you’re worried about the confidentiality of the message, you can also encrypt it so that only the intended recipient can see the contents. You cannot use username and password credentials to encrypt the message because these details are specific to the sender and, as you’ll see, you need to rely on details specific to the recipient so you can encrypt the message to that person’s requirements.

The Web service we’ll use for this example can be found at http://localhost/wscr/15/x509ws.asmx. As in the previous Web service example, this one allows you to send a message that is echoed back, but in this case it also checks whether the message was signed and encrypted. In addition, if the message that is received is encrypted and signed, the returned message will be encrypted to the X509 certificate that signed the message—ensuring that only the person who sent the message can see what the returned message contains.

Required Certificates

To use this example, you must generate two certificates—one for the client and one for the server. The details of how to create both client and server certificates were covered earlier in the chapter.

Once the server certificate has been added to the Local Computer certificate store, the public key for the certificate must be exported and added to the Enterprise Trust folder of the Current User certificate store.

In the real world, the client and server would be on separate machines, but for our examples we’re running both the client and the server on the same machine. Rather than having the client application access the Local Computer certificate store, we’ve chosen to place the public keys in the Enterprise Trust folder of the Current User certificate store. This is more likely to occur in the real world. Only administrators can add certificates to the Local Computer certificate store—others can’t add to the Local Computer certificate store, but they can add to the Enterprise Trust folder of their own certificate store.

The Client

The client for this example is the X509Client application, which you’ll find among the book’s sample code. The user interface for the application is shown in Figure 15-5. As you can see, it allows you to enter a message you want echoed as well as specify whether you want the message signed or encrypted using X509 certificates that you specify.

click to expand
Figure 15-5: The X509Client application

The two list boxes are populated as the form is loaded, in the same way as in the AuthenticationClient application except that the encryption list box is populated from the Enterprise Trust folder of the Current User certificate store rather than from the Personal folder.

After you enter the message you want to echo and select the signing and encrypting options along with the X509 certificates you want to use, the click handler for the Echo button is called.

If you’re signing the message, you open the Personal folder of the Current User certificate store and retrieve the selected certificate. We’ll create an X509SecurityToken object from the certificate and add this to the message, as we did when we used X509 certificates for authentication:

// open the current user certificate store X509CertificateStore certStore = X509CertificateStore.CurrentUserStore     (X509CertificateStore.MyStore); certStore.OpenRead(); // get the certificate and create the security token  X509Certificate certSignCertificate =     certStore.FindCertificateBySubjectString(cboCertsSign.Text)[0];  X509SecurityToken secSignToken = new     X509SecurityToken(certSignCertificate);                       // add public key to message proxy.RequestSoapContext.Security.Tokens.Add(secSignToken);

Although we said earlier that adding the public key of an X509 certificate to the message is pointless, we added it here to simplify the work that has to occur on the server. To check the message signature, the server needs to know the public key of the signer of the message. If the client adds the public key to the message, the Web service won’t need to have every public key that is to be used stored on the server. The Security input filter automatically uses the public key in the message to verify the signature of that message, and the Security output filter can then use this public key to encrypt the message back to the caller, as you’ll soon see.

To sign the message, we create a Signature object, passing in the SecurityToken object that we’re using, and we add this to the Security.Elements collection of the request’s SoapContext object. Finally, we close the certificate store.

// create the signing details and add to the message Signature certSignature = new Signature(secSignToken); proxy.RequestSoapContext.Security.Elements.Add(certSignature); // close the certificate store certStore.Close();

The code to encrypt the message is similar to the code that we use for signing, as you can see in the following listing:

// open the local machine certificate store X509CertificateStore certStore = X509CertificateStore.CurrentUserStore     (X509CertificateStore.TrustStore); certStore.OpenRead(); // get the certificate and create the security token  X509Certificate certEncryptCertificate =     certStore.FindCertificateBySubjectString(cboCertsEncrypt.Text)[0]; X509SecurityToken secEncryptToken = new     X509SecurityToken(certEncryptCertificate);   //  create the encryption details and add to the message EncryptedData encData = new EncryptedData(secEncryptToken); proxy.RequestSoapContext.Security.Elements.Add(encData); // close the certificate store certStore.Close();

We open the Enterprise Trust folder rather than the Personal folder, retrieve the certificate we’re after, and use this to create an X509SecurityToken. Whereas when we signed the message we created a Signature element, here we create an EncryptedData object and add this to the Security.Elements collection.

That’s all there is to it—WSE hides from us all of the intricacies of requesting the keys to sign and encrypt the message from the SecurityToken. By simply adding Signature or EncryptedData objects to the Security.Elements collection of the request SoapContext, we’ve specified that we want the message signed and encrypted and provided the information required to do so.

If we add a Signature object to the Security.Elements collection, the Security output filter will sign the message using the private key of the X509 certificate we used to create the Signature object, by adding a <wsse:Signature> element to the Security SOAP header and modifying the SOAP body to reference the <wsse:Signature> that was added. If we add an EncryptedData object to Security.Elements, the Security output filter will encrypt the message using the public key of the X509 certificate that we used to create the EncryptedData object, by adding an <wsse:EncryptedKey> element to the SOAP header and replacing the plain-text SOAP body with the encrypted version. We looked at the changes that the Security output filter makes to the SOAP message in Chapter 12, so we won’t go into any specific details here.

The Web Service

On arrival at the Web service, the Security input filter checks that all the security details for the message are correct before control is passed to the method that was called. During the checking process, the request’s SoapContext object is populated with the details as they are extracted from the incoming message.

The Security input filter verifies the message signature using the public key that was attached to the message—or, if that public key is not available, the filter looks in the Personal folder of the Local Computer certificate store for a certificate that matches the required public key. If there are any problems with the signing, the filter raises an exception.

If necessary, the filter also decrypts the message, searching for the private key in the Personal folder of the Local Computer certificate store by default. As you saw earlier, you can configure WSE to look in the Personal folder of the Current User certificate store. If a private key cannot be found to decrypt the message, an exception is raised automatically.

Once the security details have been verified, control is passed to the called method, and it is up to the method to enforce the necessary security requirements (whether the message has to be signed, and so forth).

Our sample Web service won’t enforce any security requirements; it will simply return to the caller an indication of whether the message the service received was signed or encrypted or both. If the message was both signed and encrypted, the Web service will also encrypt the return message using the X509 certificate that signed the message that was received.

Once control has been passed to the correct method, the first thing the method checks (as with all the other WSE-enabled Web services) is that a request SoapContext object is available. If the WSE message is properly formatted, the method will then check how many objects are in the Security.Elements collection of the request’s SoapContext object. If there are zero elements, we know that the message was neither signed nor encrypted, and we can simply echo the received message back to the caller.

If we find objects in the Security.Elements collection, we know that the message has been signed or encrypted and we can then proceed to check what was done to the message.

We first create three local variables that are used to store the information that we retrieve from the incoming message. We have Boolean flags that are set to true if the incoming message is encrypted or signed and an X509SecurityToken that contains the token that signed the message. (We’ll need this if we determine that the response message is to be encrypted.)

bool boolEncrypted = false; bool boolSigned = false; X509SecurityToken secSigningToken = null;

We then iterate through the Security.Elements collection and check to see what type of object that we’re looking at.

If we have a Signature object, we know that the message has been signed. We used the default options for what part of the message we’re signing, and you’ll see later in the chapter how to sign the different parts of the message. For now, all you need to know is that the body of the message is signed by default, and that you check that this is the case. To perform this check, we perform a bit-wise AND with the SignatureOptions property of the Signature object and the IncludeSoapBody value from the SignatureOptions enumeration. If we don’t get a zero value, we know that the message body was signed. If the message was signed, we set the Boolean flag to true and store the SecurityToken corresponding to the Signature object:

// is this a signature element if(secElement is Signature) {     // cast to a real signature object     Signature secSignature = (Signature)secElement;     // is the body signed     if ((secSignature.SignatureOptions          & SignatureOptions.IncludeSoapBody) != 0)     {         // set the flag that we need         boolSigned = true;         // get the encryption token that we need         secSigningToken = (X509SecurityToken)secSignature.SecurityToken;     } }

The process for checking that the message was encrypted is similar to that for checking that it was signed. If the object we’re looking at is an EncryptedData object, we know that the message has been encrypted and we can perform the necessary checks to see what was encrypted. As with signing the message, we use the default settings for what parts of the message are encrypted, and we check the TargetElement.LocalName of the EncryptedData object to determine whether the body element is encrypted. If it is, we set the Boolean flag to true.

// is this an encryption element if(secElement is EncryptedData) {     EncryptedData secEncrypted = (EncryptedData)secElement;     // is the body of the message encrypted     if(secEncrypted.TargetElement.LocalName == "Body")     {         boolEncrypted = true;     } }

Once we step through the Security.Elements collection, we create the return message for the method using the two Boolean flags to modify the string—this is just some simple code that checks which Boolean flags are set and appends different parts to the output string.

After we create the message that we’ll return to the caller, we decide whether to encrypt the message. If the request was encrypted and we have a public token for the caller (which will be the case if the message was signed), we encrypt the return message using the available public key:

// do we need to encrypt the return if(boolEncrypted == true && boolSigned == true) {     EncryptedData encData = new EncryptedData(secSigningToken);     respContext.Security.Elements.Add(encData); }




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