The WS-Security UsernameToken Profile 1.0 defines how the username and password can be encapsulated into a security token.
The profile defines two different ways to transmit the password (or password equivalent):
· wsse:PasswordText: the password is sent in clear-text
· wsse:PasswordDigest: a digest derived from the password is sent
As documented on MSDN, only the first option is supported by WCF’s built-in UsernameToken. This token is used in scenarios where client credential is set to Username.
The second option, wsse:PasswordDigest, is the subject of this post.
Besides WCF’s predecessor – Web Services Enhancements –, other 3rd-party SOAP protocol stacks (e.g. Axis) support the digest version, too. This leads to interoperability problems with WCF, and we regularly get Support Requests from our customers to get help on this interoperability issue.
Security tokens are most of the time not easy matter, but probably the UsernameToken is one of the simpler tokens. So after I began writing a custom token that supports the wsse:PasswordDigest, this indeed proved to be doable.
Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that. You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.
You can download the sample from here.
The custom token builds on the CustomToken sample in the Windows SDK’s WCF Samples section. This lays out the skeleton of a custom token by implementing a simple credit card token. Actually, the MSDN documentation for WCF security classes often showcases parts from this credit card sample, where applicable.
Like this stock sample, our UsernameToken token won’t support most of the advanced concepts, simply because they are not possible with this type of token. One of such would be, for example, to use this token for encryption.
This also means that the implementation’s most interesting part is generating the password digest. The rest of the code is plumbing that makes integration with WCF seamless.
The sample Visual Studio 2010 solution consists of 5 projects:
Sample client using the new token
A few common types that client and service share
Sample IIS-hosted service using the new token
The new token’s implementation
A test project that demonstrates WSE3 interoperability on token-level
Here’s a list of the most important classes and their role.
Allows using the new token by providing username and password to the client
Manages the new tokens on client-side
Simply holds username and password
Retrieves password for a given username at the service side for validation
Serializes and deserializes the token
Allows using the new token on service-side
Core token class dealing with cryptography
Authenticates the new tokens
Issues claims on the new token
Describes the new token’s capabilities
Creates the new token
This central type deals with cryptography to produce the password digest according to the requirements of the WS-S UsernameToken Profile:
Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) )
This is done in GetPasswordDigestAsBase64 as follows:
public string GetPasswordDigestAsBase64()
// generate a cryptographically strong random value
RandomNumberGenerator rndGenerator = new RNGCryptoServiceProvider();
// get other operands to the right format
byte time = Encoding.UTF8.GetBytes(GetCreatedAsString());
byte pwd = Encoding.UTF8.GetBytes(_usernameInfo.Password);
byte operand = new byte[_nonce.Length + time.Length + pwd.Length];
Array.Copy(_nonce, operand, _nonce.Length);
Array.Copy(time, 0, operand, _nonce.Length, time.Length);
Array.Copy(pwd, 0, operand, _nonce.Length + time.Length, pwd.Length);
// create the hash
SHA1 sha1 = SHA1.Create();
Fortunately, this isn’t too complicated thanks to the rich .NET class library available to us:
1. Create a cryptographically strong random value, which will be the nonce. This can be used to provide defense against replay-attacks with the combination of a nonce-cache in the service (not part of the sampe).
2. Take the time the token was created at, and transform it to yyyy-MM-ddTHH:mm:ssZ format. Encode it in UTF8 byte array.Note that knowing the time is useful if we don’t want the service’s nonce-cache grow unlimited.
3. Encode in a UTF8 byte array.
4. Allocate a byte array large enough to hold all of this data, and concatenate them.
5. Create the SHA-1 hash.
6. Return the base64 representation.
Validating an incoming token takes simply re-building the hash from the password associated with the username (service must know this), and the time and nonce already stored in the incoming token:
public bool ValidateToken(string password)
byte pwd = Encoding.UTF8.GetBytes(password);
byte createdBytes = Encoding.UTF8.GetBytes(GetCreatedAsString());
byte operand = new byte[_nonce.Length + createdBytes.Length + pwd.Length];
Array.Copy(createdBytes, 0, operand, _nonce.Length, createdBytes.Length);
Array.Copy(pwd, 0, operand, _nonce.Length + createdBytes.Length, pwd.Length);
string trueDigest = Convert.ToBase64String(sha1.ComputeHash(operand));
return String.Compare(trueDigest, _usernameInfo.Password) == 0;
To test the sample, build it with Visual Studio 2010 (please ignore source control warnings, this solutio.n is part of a Team Foundation Server project), and create a virtual application on local IIS that points to the service. The client is configured to use http://localhost/servicemodelsamples/service.svc.
In the actual scenario I wrote this custom token for, the Java-based service didn’t like the addressing headers that WCF generated. In particular, the wsa:Action header had mustUnderstand set to true, and we got the error message that “Must Understand check failed for header http://www.w3.org/2005/08/addressing : Action”.
WCF doesn’t allow removing mustUnderstand from the addressing headers (unless you remove it from the serialized message). Therefore, in the sample I simply ask WCF to not add the addressing headers by setting MessageVersion to Soap11.
WS-S UsernameToken Profile 1.0 warns that digested password should be sent over secure transport.
Obviously, using an unsecure transport doesn’t protect against capturing or modifying the message payload, but I’m not sure why the password would not be protected against attacks. Anyway, I’m not a security expert, so I advise following the recommendation and use a secure transport.