UsernameToken Profile vs. WCF

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.

Usual Disclaimer

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.

Layout of the custom UsernameToken

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:

Client

Sample client using the new token

Common

A few common types that client and service share

Service

Sample IIS-hosted service using the new token

UsernameToken

The new token’s implementation

Wse3InteropTest

A test project that demonstrates WSE3 interoperability on token-level

Here’s a list of the most important classes and their role.

UsernameClientCredentials

Allows using the new token by providing username and password to the client

UsernameClientCredentialsSecurityTokenManager

Manages the new tokens on client-side

UsernameInfo

Simply holds username and password

UsernamePasswordProvider

Retrieves password for a given username at the service side for validation

UsernameSecurityTokenSerializer

Serializes and deserializes the token

UsernameServiceCredentials

Allows using the new token on service-side

UsernameToken

Core token class dealing with cryptography

UsernameTokenAuthenticator

Authenticates the new tokens

UserNameTokenAuthorizationPolicy

Issues claims on the new token

UsernameTokenParameters

Describes the new token’s capabilities

UsernameTokenProvider

Creates the new token

UsernameToken class at the core

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();

            rndGenerator.GetBytes(_nonce);

            // 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();

            return Convert.ToBase64String(sha1.ComputeHash(operand));

        }

 

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(_nonce, operand, _nonce.Length);

            Array.Copy(createdBytes, 0, operand, _nonce.Length, createdBytes.Length);

            Array.Copy(pwd, 0, operand, _nonce.Length + createdBytes.Length, pwd.Length);

            SHA1 sha1 = SHA1.Create();

            string trueDigest = Convert.ToBase64String(sha1.ComputeHash(operand));

            return String.Compare(trueDigest, _usernameInfo.Password) == 0;

        }

 

Deployment

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 https://localhost/servicemodelsamples/service.svc.

Interoperability with Axis

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 https://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.

Security note

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.

UsernameTokenSample.zip