my previous post, Mark Naughton asked an excellent question about how he'd apply WSE 2.0 security to a particular scenario. The answer highlights how to determine which SecurityToken to use in your environment, how to encrypt a UsernameToken with an X509 certificates with code and policy as well as handling authorization with X509 certificates and determining how to distinguish tokens received by a service.
Martin's scenario
An End User uses a Web-based UI Application (ASP.NET 1.1).
The Web Application talks to a Web Service (ASMX) for data storage and other processing.
The Web Service needs to identify the End User and the "direct" calling application (The Web UI App), since there may be more than one "direct" calling application.
We also want to sign and encrypt the Soap messages in both directions.
Since we're talking about adding security to a web service, we'll need WSE 2.0 installed on all of the calling applications.
What's the best SecurityToken to use?
The first thing to consider is what SecurityTokens are applicable to the scenario. Aside from custom xml or binary tokens, the three options that WSE supports out of the box are as follows.
Username Name and Password
- For - easy to construct, no distribution problems, WSE handles automatic Windows authentication and the construction of the SecurityToken.Principal for authorization.
- Against - on their own they are only as secure as the passwords.
X509 Certificate
- For - cryptographically strong, can carry extra information (e.g. certificate name).
- Against - need to manage the distribution to clients.
Kerberos Ticket
- For - powerful cryptographically, inbuilt WSE support for authentication and creating the SecurityToken.Principal for authorization.
- Against - your application and the service you access must be running on computers joined to a Kerberos realm (e.g. not good over the Internet) unless you implement a custom security token service to issue service tickets.
So any of these tokens can identify the end user or the application - it's a matter of working out which one works best for your situation. If you can handle the distribution and installation of X509 certificates to all of the calling applications, I'd suggest using them to sign and encrypt the message. In your scenario, the ASP.NET web server could create a UsernameToken to represent the End User of your web application. For best security, I'd suggest encrypting the UsernameToken with the X509 certificate (hiding the username and password/password digest).
The code would look something like this:
// ... Assume we have an X509SecurityToken and a UsernameToken, and a reference to the web service called proxy.
// Add both tokens to the SOAP envelope
proxy.RequestSoapContext.Security.Tokens.Add( x509token );
proxy.RequestSoapContext.Security.Tokens.Add( usernameToken );
/* Encrypt the username token with the X509 token.
When encrypting, WSE looks for XML elements with an Id attribute from the http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd namespace, which the username token uses.
The "#" indicates the Id with this value is local to this message */
proxy.RequestSoapContext.Security.Elements.Add(new EncryptedData( x509token, "#" + usernameToken.Id ));
// Encrypt the message body with the X509 token to ensure no one can read it.
proxy.RequestSoapContext.Security.Elements.Add(new EncryptedData( x509token ));
// Sign the message with the X509 token to ensure its integrity
proxy.RequestSoapContext.Security.Elements.Add(new MessageSignature( x509token ));
You'll also need to decide on what token type to use when sending the signed and encrypted response. Again, I'd recommend using an X509 certificate for the most cryptographically strong security. The downside is that you'll need to install the certificate on each of the clients. If you can't handle this install requirement then you are stuck with UsernameTokens.
Distinguishing the tokens on the service
Using the combination of a UsernameToken and an X509SecurityToken to represent the identity of the end user of the calling application and the identity of the calling application itself makes it easy for the web service to work out which token is which. The web service has to search through the tokens in the RequestSoapContext.Security.Tokens collection to locate each token. If you decided to use two username tokens for example, you would need to distinguish them somehow. For username tokens you could achieve this through the username values, or perhaps by giving them well-know identifiers in their securityToken.Id property. For X509 certificates you could use the certificate name.
Performing authorization
If you use a UsernameToken encrypted with the X509SecurityToken, and you don't want to send the password as plain text, then you'll need to create your own UsernameTokenManager. This is responsible for authenticating the user and creating the SecurityToken.Principal object which can be used for authorization. For the X509SecurityToken you can create a custom X509SecurityTokenManager and in the AuthenticateToken method, after calling the base class's implementation, then create your own generic principal and attach this to the SecurityToken.Principal (Ingo Rammer wrote an excellent article on this last September for MSDN, but it's disappeared. You can find my notes on it here). The benefit of doing this is, rather than just testing for the certificate name inside the service code, is that WSE Policy validation input filters inspect the SecurityToken.Principal when verifying the policy assertion.
Wrapping it all up with policy
As I've indicated in previous posts, using policy and configuration to avoid writing security code within your service is an excellent idea. You could do that in this situation as well, except that signing the username token is a little tricky to indicate in policy (you'd have to hand-craft the policy XML file as this is a little way-off the WSE Security Settings Tool territory). In a confidentiality assertion you'd need to modify the MessageParts elements in the policy file to indicate the UsernameToken's Id attribute. I'll leave this as an exercise to the reader (as my daughter is about to wake up again), but Aaron Skonnard shows how you can use XPath 1.0 to achieve this in his excellent article, WS-Policy and WSE 2.0 Assertion Handlers.