Microsoft .NET and J2EE Interoperability Toolkit (Pro-Developer)

The sample code in this chapter highlights using WS-Security between .NET and Java to show both authentication and encryption. For the authentication sample, the code will show how a Web service will accept a request only if the request is signed with a valid X.509 certificate. In the encryption sample, although the Web service request will be made over HTTP (a nonsecure transport), the sample will demonstrate how the contents of the actual request are encrypted. This encryption will be based on a symmetric key.

Authentication Using WS-Security

The first part of the WS-Security sample will show authentication between two clients (one a .NET client and the other a Java client) and two Web services (again, one in .NET and one in Java). The .NET client and service will use WSE, whereas the Java client and service will use GLUE.

The sample works by applying WS-Security headers to the client Web service requests , based on an X.509 certificate. The Web services will allow methods to be called only if they are made by a client with a correctly formed request that has been signed by such a certificate.

To get the sample running, we first must configure the Web services and set up the authentication mechanisms.

Configuring the .NET Web Service

The first task is to set up the .NET Web service. Taking our stock-trading theme from previous chapters forward, this Web service will return a recommendation for the top stock of the day. To ensure that this information is accessed only by authenticated clients, the Web service will be protected by using WS-Security.

The Web service itself can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\dotNET\WebService directory. Navigate to this directory and build the sample code using the provided NAnt script by entering nant at the command prompt. As with the previous .NET Web service samples shown in this book, we need to create a virtual directory to host this Web service.

To do this, launch IIS from the Start\Program Files\Administrative Tools program group . Expand through to the Default Web Site option, right-click, and select New\Virtual Directory. For the virtual directory alias, type dotNETAuthWebService , as shown in Figure 13.4.

Figure 13.4: Creating the .NET WS-Security Authentication virtual directory.

Although this can be changed later, this exact name is important for the WS-Security authentication sample to run correctly. Click the Next button, and set the directory to C:\Interoperability\Samples\Advanced\WS-Security\Auth\dotNET\WebService , as shown in Figure 13.5.

Figure 13.5: Setting the path for the virtual directory.

Click the Next button, and accept the defaults for the remaining pages to complete the wizard. The secure .NET Web service is now set up. We can verify this by browsing to http://localhost/dotNETAuthWebService/StockService.asmx . If the Web service has been successfully deployed, the operations screen, as shown in Figure 13.6, will be displayed.

Figure 13.6: Validating the secure purchase recommendation operation.

As you can see, the StockService exposes a single method named GetSecurePurchaseRecommendation . Although we can see this method by using this interface, we cannot call it from the browser. If you try this, an exception will be thrown indicating that only SOAP requests are allowed to make such calls.

System.ApplicationException: Only SOAP requests are permitted.

If you get a Web page indicating that the page cannot be displayed (HTTP 500 ”Internal Server error) in Microsoft Internet Explorer, ensure that the Show Friendly HTTP Error Messages setting is disabled in the options within the browser itself, as shown in Figure 13.7.

Figure 13.7: Disabling the Show Friendly HTTP Error Messages setting in Internet Explorer.

The .NET Web service is now configured. Using GLUE, we will now configure the equivalent Web service in Java.

Configuring the Java Web Service

The Java Web service can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\Java\WebService directory. As with previous examples, publishing the Web service itself is performed by using the Ant script in this directory to build and run the sample. To do this, type start ant run at a command prompt in this directory.

Once published, the WSDL of the Web service is available at http://localhost:8004/JavaWSSWebService/StockService.wsdl . As can be seen in the WSDL document, the Web service exposes a method named GetSecureSaleRecommendation , the opposite of the one exposed by the .NET Web service.

Setting Up Certificates

The two Web services required for the sample are now set up. Before we can build and run the clients that are going to call these Web services, we must first create some X.509 certificates.

More Info

X.509 is a certificate standard based on previous X.500 directories and is used to authenticate identities on the Internet. Certificates are issued by CAs (Certificate Authorities), of which there are a number of trusted roots (VeriSign, Thawte, and so on). A certificate itself consists of a public key and relates to a private key normally held in a separate location ”a technique known as asymmetric encryption . When a certificate is sent over a network it will consist of the public key, and the private key will be retained by the sender. For encryption, the public key will be used to encrypt and the private will be used to decrypt. For authentication, the private key will be used to encrypt a hash of the message and the public key will be used to decrypt the hash to validate the signature. For more information on cryptography and algorithms used in asymmetric encryption, I recommend Applied Cryptography: Protocols, Algorithms, and Source Code in C, Second Edition by Bruce Schneier (John Wiley & Sons, 1995).

Creating the Windows X.509 certificate

The certificate will be used to digitally sign the Web service request. For the purposes of these samples, we'll create two test certificates by using tools supplied with both the .NET Framework and the J2SE (Java 2 Standard Edition). To allow these certificates to work, we'll turn off some security guards within the sample. In a production system, you can imagine how these certificates would be issued and controlled by a central or third-party CA.

Navigate to the C:\Interoperability\Samples\Advanced\WS-Security\Certs directory. This is the directory where the certificate will be created. To create the Windows X.509 certificate, we will use a tool named MakeCert.exe. This tool can normally be found in the Framework SDK directory (for example, C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\bin); the latest edition can also be installed with the Platform SDK. If you haven't already, you should add this directory to the system PATH (or use a Visual Studio .NET 2003 command prompt) to allow it to be run from the sample directory. Use the following command to create the certificate:

makecert n "CN=Simon Guest, OU=Authors, O=Microsoft Press, L=Redmond, ST=WA, C=US" ss my dotNETClient.cer

This command will create an appropriate test certificate that can be used with the sample code. The CN (common name) details are optional for this tool, and they can be adjusted to reflect your own settings within this sample.

Creating the Java certificate

To create the Java certificate, a tool named keytool .exe is used. This tool can be found in the bin directory of your J2SE installation, and again it should be made accessible from the sample directory.

This tool works a bit differently from the MakeCert tool in .NET, as it stores all certificates and keys in a single file. (The .NET tool creates an individual file for each certificate.) To create the certificate and set up this store, use the following command:

keytool keystore keystore.db genkey alias JavaClient keyalg rsa

This command will create a new key store (named keystore.db) and generate a new key (certificate) named JavaClient that will be used for signing requests. When running this command, you'll be prompted for a password for the key store itself. Enter StrongPassword ” this password will also be used in the sample code.

Enter keystore password: StrongPassword

After the key store is set up, you'll need to provide some information about the certificate itself. This will include your name, organizational unit (department), organization, and location ”similar to the information that was passed to MakeCert.exe on the command line. Enter these details as they apply to you ”this information is not specific to the sample. For my details, the following information was used:

What is your first and last name? [Unknown]: Simon Guest What is the name of your organizational unit? [Unknown]: Authors What is the name of your organization? [Unknown]: Microsoft Press What is the name of your City or Locality? [Unknown]: Redmond What is the name of your State or Province? [Unknown]: WA What is the two-letter country code for this unit? [Unknown]: US

At the end of the details, you'll be prompted to confirm the certificate details (enter yes for this) and press Return to acknowledge that the password to access this certificate will be the same as the one to access the key store itself.

Is CN=Simon Guest, OU=Authors, O=Microsoft Press, L=Redmond, ST=WA, C=US correct? [no]: yes Enter key password for <JavaClient> (RETURN if same as keystore password):

If all steps were completed successfully, you will have two files in the C:\Interoperability\Samples\Advanced\WS-Security\Certs directory: the Windows certificate (dotNETClient.cer) and the Java certificate stored in keystore.db.

Running the .NET Client

Now that we have the two Web services configured and have created suitable X.509 test certificates, let's build and run the client code in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\dotNET\Client directory. The first thing that the client requires is a certificate to use to sign the request. This is selected through a simple dialog box, shown in Figure 13.8, that lists all personal certificates in the Windows key store.

Figure 13.8: Selecting a certificate to sign the Web services request.

Select the certificate that was created in the previous section. Use the View Certificate option to verify the details of the certificate. Click OK to continue. The client will now display the details for the selected certificate (your details will vary depending on what you entered during the certificate creation earlier):

Key Name : C=US, S=WA, L=Redmond, O=Microsoft Press, OU=Authors, CN=Simon Guest Key ID of Certificate selected : zVEz4a8D/vVPjnlOYCSPm1p+O8w=

Using this certificate, the client now makes a request to both the .NET (WSE) and Java (GLUE) Web services, signing the requests as appropriate. The .NET (WSE) Web service returns a recommended stock:

Calling the WSE Service... Secure stock recommendation from the WSE Service: Ticker: ASKI Name: Alpine Ski Price: 32.45 Previous: 32.09 Volume: 45

The Java (GLUE) Web service returns a similar result:

Calling the GLUE Service... Secure stock recommendation from the GLUE Service: Ticker: TRRE Name: Trey Research Price: 9.35 Previous: 9.48 Volume: 93

Running the Java Client

The Java client makes the same authenticated calls to the .NET and Java Web services and works in much the same way. The client, located in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\Java\Client directory, produces the following output after it is built and run:

Calling the WSE Service... Secure stock recommendation from the WSE Service: Ticker: ASKI Name: Alpine Ski Price: 32.45 Previous: 32.09 Volume: 45 Calling the GLUE Service... Secure stock recommendation from the GLUE Service: Ticker: TRRE Name: Trey Research Price: 9.35 Previous: 9.48 Volume: 93

Tracing the Output

So far we have seen how both the .NET and Java client can use WS-Security to make an authenticated call to either a .NET or Java Web service. On the surface, however, this isn't apparent because when simply running the examples, the calls to the two services look like any other calls. With the exception of the calls being prompted for a certificate in the .NET client sample, there is little to indicate that these calls are being authenticated.

To prove that they are, we can check under the covers of the two client calls and observe the request and responses in their native SOAP format. One of the easiest ways to see the underlying call is to enable logging on the Java client. To do this, edit the Client.java file in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\Java\Client directory and uncomment the following line:

electric.util.log.Log.startLogging("SOAP");

Rebuild and run the Java client. What you should now observe is that the SOAP messages sent to and from the Java client are now logged to the console.

The important element to observe in the call is the binary token itself. This is sent as part of the < wsse:Security > SOAP header:

<wsse:Security xmlns:wsse='http://schemas.xmlsoap.org/ws/2002/07/secext'> <wsse:BinarySecurityToken ValueType='wsse:X509v3' EncodingType='wsse:Base64Binary' xmlns:wsu='http://schemas.xmlsoap.org/ws/2002/07/utility' wsu:Id='electric-id-8575EC20-4536-59E5-2E49-41E5030EC2E6'> MIICTjCCAbcCBD6uq/owDQYJKoZIhvcNAQEEBQAwbjELMAkGA1UEBhMCVVMxCzAJBgNVB AgTAldBMRAwDgYDVQQHEwdSZWRtb25kMRgwFgYDVQQKEw9NaWNyb3NvZnQgUHJlc3MxED AOBgNVBAsTB0F1dGhvcnMxFDASBgNVBAMTC1NpbW9uIEd1ZXN0MB4XDTAzMDQyOTE2NDQ 0MloXDTAzMDcyODE2NDQ0MlowbjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYD VQQHEwdSZWRtb25kMRgwFgYDVQQKEw9NaWNyb3NvZnQgUHJlc3MxEDAOBgNVBAsTB0F1d GhvcnMxFDASBgNVBAMTC1NpbW9uIEd1ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ KBgQChuZXZYPS+kVPsMc5Nl0x2TLrzede/ANP7KajAGC/lpYwRmaaBlzwQH1qC2wySi2g taQ9af781FsQjvjFPeTeUBfyDrUG93HLwr3NL/mEXt9YnlYJW6HcqkM1OpG8kgQm+4Bv9 K5PrZF/FI43Hnq02/re983HgGsUcC46P3QVceQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBA HV4frsL6n5CC3a9X1Wlbp7IgMskFYMuIsGAaPkQd6nGQYoLy+IuHuXdoewi4ujB1stVvH rnwPTr3gc1oNGtw6Pt2wg6M6sFJQte0NfKt5ZCKNyP85ORaIrzaRFDHgeOtmzsdJbQuPU +7C0ECKBKReEd6ZL0hFEv3aKcMu5imZRR </wsse:BinarySecurityToken>

This is the binary representation of the public portion of the certificate itself (note that your version might differ from this). The actual signature of the Web service request comes slightly after this, in an element named < SignatureValue >:

<ds:SignatureValue> YV9N+XtUJ4qRZseqK84KOSbiTotJqjtETXh7XOOadOzUk9Jl+WIPhUCKdTGPrlkdKr8jY XfXhyrn0CTAKw1Dz46ARJ2OdT/8TriW6yB3uduPPwtRK+LK6i1gKEOMGCC/bzfy33Kxiv rVDgMq4dVSWnDqivP0ZaILAKe2gZ04weE= </ds:SignatureValue>

This element is a hashed value of the request itself, signed by the private key of the certificate. To compute whether the signature is valid, the service will use the public key to decrypt the hash value of the request, recalculate what should have been the hash value of the request, and compare the two. If they are the same, the signature and contents within are deemed valid.

In the response portion of the SOAP output, notice how the response does not contain any authentication ”we could certainly do this by applying the same principles and mechanisms for the Web service itself. Notice also that the data returned back to the client is sent in as clear text:

<soap:Body soap:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'> <n:GetSecureSaleRecommendationResponse xmlns:n='x'> <Result> <Ticker>TRRE</Ticker> <Name>Trey Research</Name> <Price>9.35</Price> <Previous>9.48</Previous> <Volume>93</Volume> </Result> </n:GetSecureSaleRecommendationResponse> </soap:Body>

As we've seen in these message outputs, signing the message does not perform any encryption of the message data itself ”it just acts as a mechanism to prove the details of the sender, which prevents spoofing. Both the request for data and the response are sent in plain XML. To ensure the privacy of this data, we need to use WS-Security to encrypt the message, a process we'll be looking at in the second half of this chapter.

Proving the Sample

We've seen in the previous section the type of SOAP headers that are created when using WS-Security on the client, but with this authentication example, how do we prove that the Web services cannot be called without the correct authentication mechanism? The easiest way to do this is to edit one of the clients and comment out the code that applies the WS-Security context. If we take the .NET client, for example, we can see how this is performed.

Navigate to the C:\Interoperability\Samples\Advanced\WS-Security\Auth\dotNET\Client directory, and edit the Client.cs file. Comment out the following lines in code:

wpReqContext.Security.Tokens.Add(token); wpReqContext.Security.Elements.Add(new Signature(token));

Save the file, rebuild the sample, and rerun. Upon rerunning the sample, you will still get prompted for the X.509 certificate to use to sign the Web service request. Because these certificate details are not being added to the request (which is the result of them being commented out), you will see the following:

Calling the WSE Service... Unhandled Exception: System.Web.Services.Protocols.SoapException: System.Web.Services.Protocols.SoapExceptio n: The security information supplied was not valid. at dotNETAuthWebService.StockService. GetSecurePurchaseRecommendation() at System.Web.Services.Protocols.SoapHttpClientProtocol. ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall) at System.Web.Services.Protocols.SoapHttpClientProtocol. Invoke(String methodName, Object[] parameters) at WSEWebService.StockService.GetSecurePurchaseRecommendation() at Client.Client.Main(String[] args)

The preceding request was generated by the Web service itself. The Web service was expecting an authenticated request and instead received one that had not been signed. Logic within the Web service detected this, and the exception was thrown. This result can also be validated against the Java Web service by commenting out the relevant lines in the Java client.

How the Samples Work

The samples contain three key elements: the Web services containing the required configuration to allow WS-Security “style messages, the client files that contain the code for deciding which certificate to use, and the proxy files (which require some modification from their original state).

First, let's take a look at the .NET Web service. This can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\dotNET\WebService directory. The StockService.asmx.cs class file shows the WebMethod attribute for GetSecurePurchaseRecommendation :

[WebMethod] public Stock GetSecurePurchaseRecommendation() { SoapContext requestContext = HttpSoapContext.RequestContext; if (requestContext == null) throw new ApplicationException("Only SOAP requests are permitted."); if (!IsValid(requestContext)) { throw new SoapException("The security information supplied was not valid.", new System.Xml.XmlQualifiedName("Bad.Security", "http://www.microsoft.com/interoperability/" +"dotNETWSSWebService")); } return Recommendations.Recommendation; }

Notice how the Web method first checks for a valid SoapContext (a class from the WSE libraries) before returning the recommendation. Although this is in code within this method, you could also imagine how this could be applied to an HttpModule (a class that intercepts incoming HTTP calls) such that it applies to all methods or a group of methods.

The IsValid method within the Web service uses the SoapContext to check for a valid signature. At this point, we allow any calls to come through that have been signed by a valid X.509 certificate:

if (x509token != null) { valid = true; }

The Java Web service works in a slightly different way, but it follows the same principles. Just before the Web service is hosted, a new context is created to hold the WS-Security information. This context can be found in Server.java, located in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\Java\WebService directory.

WSSContext wss = new WSSContext(); ProxyContext proxyContext = new ProxyContext(); proxyContext.setWSSContext(wss);

The WSSContext is configured by adding a new authenticator and signature guard.

WSSSignature signatureSpec = new WSSSignature(new ElementReference("/soap:Envelope/soap:Body")); wss.in.addAuthenticator(new X509NullAuthenticator()); wss.in.addGuard(new SignatureGuard(signatureSpec));

Notice how for the authenticator, an X509NullAuthenticator is used. This usage has an effect similar to the IsValid method in the .NET Web service in that this authenticator will allow any valid certificate to pass through. This approach is useful for demonstration purposes, but in production, the X509Authenticator class should be used. The X509Authenticator class relies on a preconfigured set of certificates that already exists in the key store. If an incoming request is signed by a certificate that is not in the key store, it is ultimately rejected.

Finally, the Web service is registered with the publish method.

Registry.publish("StockService", new StockService(), proxyContext);

This sample code sets up the Web services for both the .NET and Java Web service.

The client code follows a similar path, with additional instructions wrapping the existing calls to the Web services. First, references to the proxy files are created and two SoapContext objects are derived from each individual proxy. For the .NET client (Client.cs in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\dotNET\Client directory), the code looks as follows:

WSEWebService.StockService wp = new WSEWebService.StockService(); GLUEWebService.StockService gp = new GLUEWebService.StockService(); SoapContext wpReqContext = wp.RequestSoapContext; SoapContext gpReqContext = gp.RequestSoapContext;

As the RequestSoapContext method is not normally exposed by a Web service proxy, we'll look shortly at the additional steps to create this in the proxy file.

The next step is to ask the user for a certificate. We do this by using the same function as shown in the WSE 1.0 Quickstart samples (which were included as part of the WSE installation earlier). Given the ID of a certificate, we could have also used the following code instead:

X509SecurityToken token = GetSecurityToken(""); if (token == null) throw new ApplicationException("No key provided for signature.");

The GetSecurityToken method displays a small Windows Form that displays the list of certificates available in the Windows key store for the current user. When a certificate is selected, it's returned to the client as an X509SecurityToken .

The next step is to add this token to the Tokens and Elements class within the Security component of the SoapContext object. Doing this will both add the X.509 certificate details and cause the signature to be created that we observed earlier in the SOAP output. This is done for the call to both the .NET and Java Web services.

wpReqContext.Security.Tokens.Add(token); wpReqContext.Security.Elements.Add(new Signature(token)); gpReqContext.Security.Tokens.Add(token); gpReqContext.Security.Elements.Add(new Signature(token));

Finally, the calls to the two Web services are made ”to the .NET Web service to get the recommendation for purchase, and to the Java Web service to get the recommendation for sale.

WSEWebService.Stock stock = wp.GetSecurePurchaseRecommendation(); . . . GLUEWebService.Stock stock2 = gp.GetSecureSaleRecommendation();

As these clients are dealing with separate namespaces, we are using the Stock type from each (although technically, they relate to the same type).

Not surprisingly, the Java client works in a similar fashion, but it uses the appropriate calls from the GLUE libraries. The client (Client.java, which can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\Java\Client directory) starts by defining the URLs for both the .NET and Java Web services.

String wseURL = "http://localhost/dotNETWSSWebService/StockService.asmx?WSDL"; String glueURL = "http://localhost:8004/JavaWSSWebService/StockService.wsdl";

In previous examples, we have used a helper class to bind to the Web service. In this client, however, we need to create a context that holds the WS- Security information. As this context is not supported by any helper methods, we'll perform the binding in the client itself. In a production system, you could imagine how we could also use the agent-service pattern that was first shown in Chapter 6 to abstract these types of calls to a different class.

After the context has been created, the certificate used to sign the requests is loaded from the key store. Unlike with the .NET client, we have chosen to hard-code the location of the key store in the client source itself. The aliases and passwords used in this code to access the key store should equate to the ones used when the store was created.

ProxyContext proxyContext = new ProxyContext(); KeyStore keyStore = loadKeyStore("..\..\..\Certs\keystore.db", "StrongPassword"); PrivateKey privateKey = (PrivateKey) keyStore.getKey("JavaClient", "StrongPassword".toCharArray()); X509Certificate cert = (X509Certificate) keyStore.getCertificate("JavaClient");

After the certificate has been loaded, the signature is created and added to the ProxyContext .

WSSContext wss = new WSSContext(); proxyContext.setWSSContext(wss); WSSSignature signatureSpec = new WSSSignature(new ElementReference("soap:Envelope/soap:Body")); signatureSpec.setCertificate(cert); signatureSpec.setPrivateKey(privateKey); wss.out.addSignature(signatureSpec);

Notice how in this code the setCertificate and addSignature methods correspond with Tokens.Add and Elements.Add in the .NET sample.

Finally, the binding for each Web service is created, and the relevant stock-recommendation call is made.

WSEWebService.IStockServiceSoap wseService = (WSEWebService.IStockServiceSoap)Registry.bind(wseURL, WSEWebService.IStockServiceSoap.class,proxyContext); GLUEWebService.IStockService glueService = (GLUEWebService.IStockService)Registry.bind(glueURL, GLUEWebService.IStockService.class,proxyContext); WSEWebService.Stock stock = wseService.GetSecurePurchaseRecommendation(); GLUEWebService.Stock stock2 = glueService.GetSecureSaleRecommendation();

The final piece for linking the client and Web service is the proxy file for each. Although the sample contains a proxy file that has been pregenerated, it's worth going through the steps required to create a proxy file to enable this authentication.

The proxy files for the .NET client can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Auth\dotNET\Client directory. The two files are named WSEWebService.cs and GLUEWebService.cs. Both were generated with the WSDL.EXE tool that was used extensively throughout Chapter 6, but two slight modifications were made.

If you open one of these files, you'll notice that the StockService class inherits from WebServicesClientProtocol instead of System.Web.Services.Protocols.SoapHttpClientProtocol .

public class StockService : WebServicesClientProtocol

The WebServicesClientProtocol is actually part of the WSE libraries, and as a result, our second modification requires the library to be referenced within the class itself.

using Microsoft.Web.Services;

This inheritance allows the client to make the call to the RequestSoapContext method to add the X.509 token and sign the request. These modifications are required to use the WSE, and they can be made either by hand or with the WSE Settings Tool for Visual Studio .NET, which was mentioned earlier in this chapter.

For the Java client (Client.java in C:\Interoperability\Samples\Advanced\WS-Security\Auth\Java\Client), the proxy files for the two Web services are located in two subdirectories: GLUEWebService and WSEWebService. As the wsdl2java tool produces more than one file for each, this is a cleaner solution than having only one subdirectory. Although the proxy files do not need to be altered , the MAP files that are generated need to be placed in the same directory as the calling client. To enable this, the MAP files from the generated wsdl2java output were renamed to WSEWebService.map and GLUEWebService.map, respectively, and placed in the same directory as the client source. These MAP files differentiate among the Stock types exposed by each of the services.

Other Methods of Authentication

In this sample, we've seen how X.509 certificates were chosen to demonstrate authentication between the .NET and Java clients and Web services. Although the process is not shown in the sample code, in addition to authenticating with an X.509 certificate, both Microsoft WSE 1.0 and GLUE 4.0.1 support authentication with a Username/Password token or a binary security token. For more information, consult the product documentation for each.

Thinking About Authorization

This authentication sample using WS-Security has shown how either a .NET or Java Web service can be created to allow only authenticated requests from clients ”requests that in this instance were signed with an X.509 certificate. What the example doesn't show is any authorization of the request once it has been accepted.

In a production solution, you would likely encounter a need to restrict both incoming requests that are not signed and incoming requests that are signed by a certificate that is not recognized. Performing this level of authorization will require additional work by both the .NET and Java Web services.

For the .NET Web service, achieving this level of authorization might involve expanding the IsValid method so that it checks the details of the certificate, or it might involve matching up the public parts of the certificate to a certificate stored in an Active Directory. For the Java Web service, this might involve restricting the certificates to the ones stored in the key store (by using the X509Authenticator class) or by using something more customized, where parts of the certificate are matched against a Java Authentication and Authorization Service (JAAS) realm.

Encryption Using WS-Security

We have now seen how WS-Security can be used to provide authentication between clients and Web services in both .NET and Java. While authentication enables the Web service to restrict access based on credentials, both the request and the response are sent in clear text. By examining the trace output of a message, anyone can see the data being passed.

A second area of the WS-Security specification allows both the request and the response to be encrypted. This encryption can be either based on a certificate (a public and private key mechanism) or use a symmetric key (a key that both the client and the Web service agree on).

In the sample code in the second half of this chapter, we'll assume the client wants to pass a credit card number to the recommendations stock service. While authentication allows the service to accept or deny the request by verifying the integrity of the data, encryption is required to ensure the privacy of the data that will be passed. To enable the sample to work, the method that's used for returning recommendations will accept a parameter of type String , which will be used for the credit card details.

Configuring the .NET Web Service

As with the authentication example, to show the samples in action we need to create a virtual directory to host the .NET Web service. The sample code used for the Web service can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Enc\dotNET\WebService directory. Use the accompanying nant script to build the sample code in this directory.

To create the virtual directory using IIS, right-click the Default Web Site icon within the administration tool and select New/Virtual Directory. For the alias of the Web service, use dotNETEncWebService .

Set the path of the files correctly ( C:\Interoperability\Samples\Advanced\WS-Security\Enc\dotNET\WebService ) in the next stage of the wizard.

Accept the remaining default settings to create the virtual directory. Again, the publishing of the Web service can be validated by browsing to http://localhost/dotNETEncWebService/StockService.asmx . The method signature for the GetSecurePurchaseRecommendation should now require a String to represent a credit card, as shown in Figure 13.9.

Figure 13.9: Validating the Web service with Internet Explorer.

The configuration of the .NET Web service is now complete.

Configuring the Java Web Service

The sample code for the Java Web service can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Enc\Java\WebService directory. As with the previous authentication sample, build this code by using the supplied Ant script. The run target can be used with the Ant script to publish the Web service on http://localhost:8004/JavaWSSWebService/StockService.wsdl .

Both the .NET and Java Web services are published. Before we run the clients, let's first look at how the symmetric key is configured.

The Symmetric Key

You might recall that with the last authentication, sample certificates were created and used by both the .NET and Java clients to sign the Web service request. Although the WS-Security specification allows certificates for encrypting data, at the time of this writing, GLUE 4.0.1 doesn't support this. In this sample, we'll show how a symmetric key can be used instead. With symmetric-key cryptography, the same key is held by both the client and the service and is used to both encrypt and decrypt the data. In a production environment, symmetric keys are often distributed with the client and little additional setup is required. Additionally, the symmetric key can be negotiated at the time of connection. This is often referred to as a handshake .

The symmetric key that will be used in this sample is the same as the default key supplied with the Microsoft WSE 1.0 Quickstart samples, and it's currently hard-coded into each of the classes for the client.

Tip

When using WS-Security in a production environment, you would of course assign a key different than the one used in the samples here. You can assign a different key by creating a new key using the cryptography APIs in either .NET or Java. In addition, you should always store the symmetric key in a separate file that is loaded at run time by the client ”and ideally , this file would be secured using a separate mechanism. These steps will help prevent malicious identity theft and spoofing and allow you to recycle the key without having to recompile any client code.

Running the Java Client

We'll first run the Java client to show the details of the message that is constructed . The sample code for the Java client can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Enc\Java\Client directory. Ensure that both the .NET and Java Web services are running by testing the URLs through Internet Explorer, and build and run the sample client code using the provided Ant script.

If the client code is successfully run, you should see the following output:

Calling the WSE Service... Secure stock recommendation from the WSE Service: Ticker: ASKI Name: Alpine Ski Price: 32.45 Previous: 32.09 Volume: 45 Calling the GLUE Service... Secure stock recommendation from the GLUE Service: Ticker: TRRE Name: Trey Research Price: 9.35 Previous: 9.48 Volume: 93

The client has successfully constructed the Web service request to both the .NET and Java Web services, and both requests contain the encrypted credit card information. If you look at the running Java Web service, you should see that the credit card detail was successfully sent to the method.

Credit card details received: 1234 5678 9012 3456 (01/05)

Looking back at the Java Client class, you'll notice that this number and expiration date are hard-coded for sample purposes.

Again, as with the authentication sample, it's reassuring to check that the underlying communication was in fact secure. To do this, we can enable SOAP logging on the Java client. Open the Client.java file, and locate and uncomment the following line of code:

electric.util.log.Log.startLogging("SOAP");

Rebuild and rerun the Java client. The console window will now show the SOAP messages that are being sent to and received from the Web service.

Looking at the first message that was sent from the Java client to the .NET Web service, you can see that no credit card number was passed as part of the request. Instead, the SOAP message contains an element named EncryptedData .

<n3:EncryptedData xmlns:ds='http://www.w3.org/2000/09/xmldsig#' Id='electric-id-59E66E3D-ED83-8900-135B-F534FF089EAB'> <n3:EncryptionMethod Algorithm='http://www.w3.org/2001/04/xmlenc#tripledes-cbc'/> <ds:KeyInfo> <ds:KeyName>WSE Sample Symmetric Key</ds:KeyName> </ds:KeyInfo> <n3:CipherData> <n3:CipherValue> AAAAAAAAAAAuIFS4iNzjpxPEihTVtlwbjL1QT5Ur/MdQGPutyfCwZ29YRk2GhpqKKsjsH 7c8j4KXCwa4P6gvIDzCyoOFXAegK9SwBU1FaG2UBW6OxceeUCjj06Y5Alw5IrIBU3phif BkXTcdsWHzxsZP9l38bcpHEpwY1/LhsU1iRG0k2WJ75HsuwZr4a+WJ3a/d2HjB9UMYf4N ONVzMt5xzhzmvu89QLkSUb0/gNZDwprVTnK10iJ9izn5k8MpvtQX774mx1YYDS+l+WXEh rMpfj5HHeV4z </n3:CipherValue> </n3:CipherData> </n3:EncryptedData>

In this message, we can see three key bits of information. First, the EncryptionMethod element lists the algorithm that was used to encrypt the data. In this case, a Triple DES with CBC (Ciphertext Block Chaining) was used. The second element of interest is KeyName . This is a String field that is used to indicate to the Web service which key was used to encrypt the data. This allows both the client and Web service to distinguish between any number of keys shared between them. Finally, the CipherValue element contains the encrypted data itself.

When we look at the reply from the .NET Web service, we can see that the returned stock recommendation was not encrypted.

<GetSecurePurchaseRecommendationResult> <Ticker xmlns= 'http://www.microsoft.com/interoperability/dotNETEncWebService'> ASKI</Ticker> <Name xmlns= 'http://www.microsoft.com/interoperability/dotNETEncWebService'> Alpine Ski</Name> <Price xmlns= 'http://www.microsoft.com/interoperability/dotNETEncWebService'> 32.45</Price> <Previous xmlns= 'http://www.microsoft.com/interoperability/dotNETEncWebService'> 32.09</Previous> <Volume xmlns= 'http://www.microsoft.com/interoperability/dotNETEncWebService'> 45</Volume> <GetSecurePurchaseRecommendationResult>

The preceding code shows us that the sample code encrypts the credit card details from the client to the Web service, but the response is sent in clear text. In the real world, you might be required to encrypt the data sent back to the client also. (Given that the information was paid for by a credit card, I would hope that it is worth encrypting!)

Encryption can certainly be applied to the response as well as to the request. The key observation here is that just because the request is encrypted it doesn't automatically mean the response is, too. This way of thinking is slightly different than in the HTTPS/SSL world, where once the secure link is established, both requests and responses are encrypted for the duration of the session.

Running the .NET Client

Before we look at how each client works, run the .NET client, the sample code for which can be found in the C:\Interoperability\Samples\Advanced\WS-Security\Enc\dotNET\Client directory. Use the accompanying NAnt script to build and run the sample code. You should see a result similar to that seen with the Java client.

How the Samples Work

First, each Web service must implement WS-Security. For the .NET Web service, this implementation is configured through the web.config file, which can be found in the directory of the Web service itself. Within the security elements of the web.config file is an entry that specifies the decryption provider:

<decryptionKeyProvider type="dotNETEncWebService.DecryptionKeyProvider, dotNETEncWebService"/>

As we can see, the provider refers to a class within the Web service itself. This class can be found in the StockService.asmx.cs file, and it overrides the DecryptionKeyProvider class method that specifies the key that will be used. As mentioned previously, this key is hard-coded in this class.

The Web method itself, GetSecurePurchaseRecommendation , does not need to make any calls to verify or decrypt the data. Verification is automatically handled by the WSE HttpHandler class.

[WebMethod] public Stock GetSecurePurchaseRecommendation(String creditCardNo) { SoapContext requestContext = HttpSoapContext.RequestContext; if (requestContext == null) throw new ApplicationException("Only SOAP requests are permitted."); if (requestContext.Security.Elements.Count == 0) throw new ApplicationException("No security information sent with request."); return Recommendations.Recommendation; }

Two checks, however, are made within this method to ensure that only SOAP requests are allowed and that some security elements were supplied with the call. These checks allow the sample to reject calls made by clients who have not encrypted the data in the request.

For the Java Web service, the WS-Security properties are configured in the Server.java class. As with the previous Java examples, you can move these settings into a configuration file. Within the Server class, the configuration is performed in three stages. First, a new BasicRealm is created, with a principal that includes an account with the username and password that match the symmetric-key settings.

BasicRealm realm = new BasicRealm("test"); String userName = "WSE Sample Symmetric Key"; String password = "EE/uaFF5N3ZNJWUTR8DYe+OEbwaKQnso"; realm.addPrincipal(userName,password);

You can find additional information on realms in the "Web Services Authentication and Authorization" section in Chapter 6. Next, a WS-Security “specific context is created. This context will be used to set the encryption properties for the hosted Web service.

WSSContext wss = new WSSContext(); ServiceContext serviceContext = new ServiceContext(); serviceContext.setWSSContext(wss);

Finally, a new class of type WSSEncryption is created and configured with the symmetric-key settings. A guard is used to set up the WS-Security context and prevent nonencrypted requests from calling the Web service. In GLUE, a guard performs a role similar to that of a filter (in the Java Servlet API) or HttpModule (in .NET). The EncryptionGuard method intercepts the message before it reaches the Web service method. If the request is not encrypted based on a preconfigured set of parameters, it is rejected.

WSSEncryption encryptionSpec = new WSSEncryption(); ElementReference reference = new ElementReference("soap:Envelope/soap:Body/*"); encryptionSpec.setReference(reference); encryptionSpec.setRealm(realm); wss.in.addGuard(new EncryptionGuard(encryptionSpec));

The .NET and Java clients work in a way similar to the authentication examples shown in the first section. For the .NET client, the proxy file to the Web service is created and adjusted to include the WSE 1.0 references. After the SOAP context has been derived from the proxy, the symmetric key is added as a new security element to the call.

SoapContext wpReqContext = wp.RequestSoapContext; SoapContext gpReqContext = gp.RequestSoapContext; wpReqContext.Security.Elements.Add(new EncryptedData(key)); gpReqContext.Security.Elements.Add(new EncryptedData(key));

This key is obtained from GetEncryptionKey method in the client code. The data for the key is in the code for this sample and is used with the SymmetricEncryptionKey class to generate the symmetric key.

SymmetricEncryptionKey key = new SymmetricEncryptionKey(TripleDES.Create(), keyBytes);

The fictitious credit card number is then created and passed to the GetSecurePurchaseRecommendation method.

WSEWebService.Stock stock = wp.GetSecurePurchaseRecommendation(creditCardNo);

This method is used for both the .NET and Java Web services.

For the Java client, a similar approach is taken. A new WSSContext (WS- Security Context) is created for the call. The symmetric key is then added to the context with the following code:

byte[] secretKeyBytes = Base64.fromBase64(symmetricKey); encryptionSpec.setSecretKeyBytes(secretKeyBytes); wss.out.addEncryption(encryptionSpec);

After the Web service is bound, the client calls both the .NET and Java Web services.

WSEWebService.Stock stock = wseService.GetSecurePurchaseRecommendation(creditCardNo); GLUEWebService.Stock stock2 = gp.GetSecureSaleRecommendation(creditCardNo);

For both clients, the recommendation returned by each of the Web services is displayed to the console.

Категории