WSE 2.0 Authentication: How To Avoid ClearText Passwords With UsernameToken

When you take the custom authentication route and write a
UsernameTokenManager (UTM), your implementation of AuthenticateToken
must return the same secret (e.g., password) used on the client side
to generate the hash/signature, depending on which option you use.

As he correctly points out, this makes security experts cringe and hide
under the bed (see Keith Brown’s cringing
response
where he proposes a solution).

The big issue is that your UsernameTokenManager needs access to the
original cleartext password. But like any good security conscious
developer, you don’t store passwords as cleartext, do you? (I sure hope
not. Bad security conscious developer. Bad!). Hopefully you do something
along the likes of what Keith suggests in his MSDN
column.
For each user, he stores a randomly generated salt value and a hash of
the cleartext password combined with salt value. The salt value is
unique per user.

Keith points out that the secret returned by the AuthenticateToken
method doesn’t have to be the actual cleartext password. It just has to
match the secret sent by the client. So if you store your passwords as
an SHA1 hash, your client just needs to hash the password before
creating the UsernameToken.

However, if you store your password as an SHA1 hash of the cleartext
password + salt value, you’re going to have to do a little more work.
Your client isn’t going to know the salt value for every user, so your
client needs a way to discover that. This may require calling a separate
web method just to query for the salt value given a user name. Service
clients would be required to store that value (probably on a “session”
basis) and use it when calling methods on the main web service.

Below is some sample code for doing just that. This assumes that user
passwords are stored as described in the aforementioned article using
salt and hash (no eggs, but do bring the ketchup). (My apologies for the
ugly formatting, I didn’t want the code to be too wide)

//Make an initial web service call to get the
//the salt value for the user "haacked".
//This should be stored by the client so its
//not called for every method of our main service.
MyServiceWseproxy=newMyServiceWse();//In order to get the salt value, a special account
//"saltAdmin" is used to call GetSalt(). This account
//only has access to this method.
//This also requires that the client app knows the;
//saltAdmin's salt value up front.
stringadminPassword=GetAdminPassword();//implementation not shown.
UsernameTokenadminToken=newUsernameToken("saltAdmin",adminPassword,PasswordOption.SendHashed);proxy.RequestSoapContext.Security.Tokens.Add(adminToken);stringusername="haacked";stringsalt=proxy.GetSalt(username);proxy.RequestSoapContext.Clear();// Hash password and salt.
stringpw="Password";//assume this came from the user.
SHA1CryptoServiceProviderhashProvider=newSHA1CryptoServiceProvider();byte[]inputBuffer=Encoding.Unicode.GetBytes(pw+salt);byte[]result=hashProvider.ComputeHash(inputBuffer);stringhashedPassword=Convert.ToBase64String(result);//Set up the user's token.
//Notice we the hashed password instead of the cleartext one.
UsernameTokentoken=newUsernameToken(username,hashedPassword,PasswordOption.SendHashed);proxy.RequestSoapContext.Security.Tokens.Add(token);//Make the actual service call.
proxy.SomeWebServiceMethod();

The AuthenitcateToken method of your custom UsernameTokenManager class
can now just return the hashed password value for the calling user from
your data store and everything will work just fine and security experts
can come out from under the bed.