Last week's exercise taught you how to install a sample X.509 certificate and configure your applications to take advantage of their improved security. This week's exercise gets into policies, which allow for greater automation of certain aspects of security. It is the third in a series of exercises that are part of a lab titled "Web Services Enhancements 2.0: Security and Policy," written by MSDN Virtual Labs.
In the last two exercises you were able to secure the SecureInvoiceService application by requiring UsernameToken authentication, message signatures, encryption, and implementing authorization checks in your WebMethod code. You also used X.509 certificates for signing and encrypting messages, which provided stronger security in the areas of integrity and privacy.
Implementing these security features was a manual process, meaning you had to write code to introduce the functionality. This can be tedious and error-prone. A better approach would allow developers to declaratively enable security features, without having to write any code, while relying on the infrastructure take care of the details.
WS-Policy is a Web services specification that allows Web services to describe policies. A policy describes the requirements, preferences, and capabilities of a service. A policy statement consists of one or more policy assertions (see WS-PolicyAssertions and WS-SecurityPolicy). Developers can read policy statements to learn how to deal with the service appropriately. Infrastructure can programmatically inspect policy statements in order to automate the code you wrote in the previous exercises. WSE 2.0 provides support for all of these specifications as you'll see below. In this exercise, you're going to secure SecureInvoiceService with minimal code.
You'll be working in the Exercises\C\before directory.
TasksDetailed steps
Getting Started.
You can write policies for receiving messages as well as for sending messages. In this exercise you're going to write a policy that describes messages received by the Web service.
Return to Visual Studio .NET 2003.
Click File | Open | Project.
Navigate to C:\Microsoft Hands-on-Lab\DEV-HOL34\VB\Exercises\C\before.
Select PolicyInvoice.sln and click Open. This solution consists of two projects: PolicyInvoiceClient and PolicyInvoiceService.
The code in PolicyInvoiceClient is nearly identical to the SecureInvoiceClient project you started with before adding security features. The only difference is that WSE 2.0 has already been enabled.
The code for PolicyInvoiceService is identical to the SecureInvoiceService project you started with before adding security features. The only difference is that WSE 2.0 has already been enabled.
Note: it doesn't currently require any security features, such as security tokens, signatures, or encryption.
Build the solution. Run the client and verify that security is not currently in place.
Note: now your goal is to provide the same security features you implemented in the previous exercises without writing a single line of code. You'll accomplish this with a policy.
You can write a policy that requires certain security features. In this step, you're going to write a policy that requires security tokens, signatures, and encryption without having to write any code (like you did before). You're going to create the policy using the WSE Security Settings Tool.
Right click on the PolicyInvoiceService project in Solution Explorer and select WSESettings 2.0. This will open the WSE 2.0 settings tool.
On the General tab, verify that both check boxes are checked to enable WSE 2.0.
Go to the Policy tab and click Enable Policy.
In the Edit Application Policy section, click Add.
In the Add or Modify Endpoint URI dialog box, enter the following URL: http://localhost/PolicyInvoiceService/ ViewInvoices.asmx.
Note: this specifies the endpoint that you're going to describe with the policy you're creating. Make sure you get the case right as the current implementation is case sensitive.
Press OK to close the dialog. This should launch the WSE Security Settings Tool:
Press Next and select Secure a service application.
Press Next and ensure that Requires signatures and Requires encryption are checked for both the request and response messages.
Press Next and select Username for the client token type.
Press Next and in the Allowed Useror Role section, press Add Role. In the Add Role Name dialog, enter CLIENT1\User. Press OK to close the dialog.
Press Next and in the Choose X.509Certificate section, press Select Certificate. In the SelectCertificate dialog, select the WSE2QuickStartServer certificate.
Press OK to close the SelectCertificate dialog, press Next, and Finish the wizard.
Note: the wizard creates a policy that requires the following: a UsernameToken, a signature based on a DerivedKeyToken (based on the UsernameToken), and an encrypted body (where the encryption should be performed using the WSE2QuickStartServer certificate). It also specifies that the authenticated UsernameToken must belong to the MACHINE_NAME\User role in order to authorize access.
Repeat these steps to create a policy for each of the .asmx endpoints in the PolicyInvoiceService project (SubmitInvoice.asmx, ApproveInvoice.asmx, and PayInvoice.asmx). You should choose the same settings described above. The only difference will be the URL you enter after pressing Add to launch the wizard.
Go to the Security tab and click to select Allow test roots, click Yes to confirm test roots, and verify that the Store location box contains Local Machine.
Press OK to close to the WSE Settings Tool.
Note: the tool made various changes to the project's web.config file and added a new file named policyCache.config.
In Solution Explorer, verify that policyCache.config is now found in the PolicyInvoiceService project. This file was added by the WSE Settings Tool. It contains the policy statements you defined by running the wizard.
Open policyCache.config and take a look at the XML representation of the policy statements.
Open Web.config in the PolicyInvoiceService project. Notice the new entries in the microsoft.web.services2 section:
Note: the x509 element tells the WSE 2.0 infrastructure which certificate store to use and that test certificate authorities are OK. The policy element specifies which policy file to use for this application.
Build the solution.
Run PolicyInvoiceClient and test invoking an operation. At this point, you should get an error indicating: The message does not conform tothe policy it was mapped to. This is because the service now requires various security features that the client isn't currently sending in the messages. Now the client needs a policy to figure out what to send.
You can write a policy that adds security features to messages before they're sent. In this step, you're going to write a policy that adds security tokens, signatures, and encryption to messages without having to write any code (like you did before). You're going to create the policy using the WSE Security Settings Tool.
Right click on the PolicyInvoiceClient project in Solution Explorer and select WSESettings 2.0. This will open the WSE Settings Tool.
On the General tab, verify that WSE 2.0 is enabled.
Go to the Policy tab and click Enable Policy.
In the Edit Application Policy section, click Add.
In the Add or Rename Endpoint URI dialog box, leave <DefaultEndpoint>.
Note: this specifies that the policy you're about to create applies to all endpoints used by this application.
Press OK to close the dialog. This should launch the WSE Security Settings Tool.
Press Next and select Secure a client application.
Press Next and ensure that Requires signatures and Requires encryption are checked for both the request and response messages.
Press Next and select Username for the client token type.
Press Next again (you don't need to specify a role here).
Press Next and in the Trusted Certificates section, press Add. In the Select Certificate dialog, select the WSE2QuickStartServer certificate.
Press OK to close the Select Certificate dialog, press Next, and Finish the wizard.
Note: the wizard creates a policy that tells the client application to: send a UsernameToken, sign the message with a DerivedKeyToken (based on the UsernameToken), and encrypt the body with the WSE2QuickStartServer certificate.
Go to the Security tab and click to select Allow test roots, and click Yes to confirm.
In the Store location box, select Current User.
Press OK to close to the WSE Settings Tool.
Note:the tool made various changes to the project's app.config file and added a new file named policyCache.config.
In Solution Explorer, verify that policyCache.config is now found in the PolicyInvoiceClient project. This file was added by the WSE Settings Tool. It contains the policy statement you defined by running the wizard.
Open InvoiceManagerForm.vb and import the following namespace:
When you run the client application with the policy in place, WSE will look for a UsernameToken that it can use in the message. You must supply WSE with this token ahead of time by adding it to a global token cache. Open InvoiceManagerForm.vb and update ConfigureProxy to look like this:
... Private Sub ConfigureProxy(ByVal proxy As _ WebServicesClientProtocol) PolicyEnforcementSecurity TokenCache.GlobalCache. Clear() PolicyEnforcementSecurity TokenCache.GlobalCache.Add ( login.Token) End Sub 'ConfigureProxy ...
Build the solution.
Run PolicyInvoiceClient and test invoking an operation. Verify that everything works.
Close the application.
Open OutputTrace.webinfo in the PolicyInvoiceClient output directory (bin). Notice that WSE automatically adds the UsernameToken, signs the message with a DerivedKeyToken, and encrypts the body of the SOAP message with the WSE2QuickStartServer certificate. The messages now conform to the requirements of the service (as specified by the server-side policy).
When you inspect the traced message, you'll notice that the UsernameToken contains a plain text password that is not encrypted on the way out.
In this step, you're going to adjust both policy statements to enforce/require encrypting the UsernameToken that contains the plain text password.
Open policyCache.config in the PolicyInvoiceClient project.
Locate the first <Confidentiality> element and update the <MessageParts> child element to contain the wse:UsernameToken() message part as illustrated here: