Context
You are implementing Direct Authentication for an online
application consuming a Web Service that uses Web Service Enhancements (WSE)
2.0. You are using Message layer authentication. The credentials, used to prove
the identity of the calling application or user, are located in a database.
Implementation Strategy
The
WSE implementation of UsernameToken is
used to implement Direct Authentication at the message layer. The requestor passes
the subject's credentials as part of a signed and encrypted Web service request
message.
A
password is not sent across the network – instead proof-of-possession is
established by verifying an XML Signature within the message, which is
generated using the password equivalent as a signing key.
The service
authenticates the subject by recognizing the subject’s unique identifier, and
verifying the proof-of-possession associated with the identifier with the
Identity Provider.
The Web service then sends an encrypted response
back to the requestor.
Approach
Two tasks must be performed to implement Direct Authentication with
Username Token using a database.
1.
Requestor generates Web service request.
2. Service authenticates Subject and returns a response.
Each task is divided into specific steps. While you are strongly encouraged to
follow the recommendations, the steps contain enough information to allow you
to tailor a solution to your specific needs.
Requestor Generates Web Service Request
This task has three steps that are recommended:
1. Initialize
the UsernameToken
2. Sign
the Message
3. Encrypt
the Message
Each step is discussed in more detail below:
Step One: Initialize
UsernameToken
This pattern
implements a UsernameToken with the
SendNone password option.
Proof-of-possession and Data Origin Authenticity are provided by using a SHA-1
HMAC XML Signature (for more information, see the Data Origin
Authentication pattern). No
password is sent in the UsernameToken, as proof-of-possession
is provided by the XML Signature.
Note: For more information on UsernameToken, see
UsernameToken Primer.
You should use policy in WSE 2.0 to ensure that messages are
encrypted and signed.
The location of
the policy cache file must be specified in the Requestors configuration file
under the <configuration>
element, as
shown in the following code example:
<microsoft.web.services2>
<policy>
<cachename="../../Configuration/PolicyCache.config"/>
</policy>
</microsoft.web.services2>
Note:
The identity
under which an application is running must have read/execute permissions on the
policy cache file; otherwise, the Requestor will not be able to access the cache
properly.
Although the password is not sent in the
UsernameToken for authentication, the Requestor uses the password value
of the initialized UsernameToken as
the signing key for the XML Signature. A password equivalent, rather than the
password itself, is used as the key to create the XML signature. This is
because the signing key is stored in the database, and you should not store
passwords in a database as plaintext. If passwords are stored in this way, they
could be compromised immediately if an attacker gains access to the database.
The password equivalent is a salted hash value that uses the Subject’s password
plus other values to make it more difficult for an attacker to guess the
Subject’s password offline
The following formula is recommended to create the password
equivalent: SHA1 (password + username + URI). The URI is unique to the service
that the requestor is accessing. This means that if the database is
compromised, the password in only good for a specific application and not all
applications that the subject uses the same password to access.
The code example shows how to implement the password hashing
formula described above:
private string GetSaltedHash( string
userName, string password, string url )
{
HashAlgorithm algorithm = new SHA1Managed();
byte[] userNameBytes = UTF8Encoding.UTF8.GetBytes( userName.ToLower(
CultureInfo.InvariantCulture ) );
byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes( password );
byte[] urlBytes = UTF8Encoding.UTF8.GetBytes( url.ToLower(
CultureInfo.InvariantCulture ) );
byte[] value = new byte[ userNameBytes.Length + passwordBytes.Length +
urlBytes.Length ];
userNameBytes.CopyTo( value, 0 );
passwordBytes.CopyTo( value, userNameBytes.Length );
urlBytes.CopyTo( value, userNameBytes.Length + passwordBytes.Length );
byte[] hash = algorithm.ComputeHash( value );
return Convert.ToBase64String( hash );
}
The following code example shows how to initialize a
UsernameToken and add it to the policy cache:
string signatureKey = GetSaltedHash(userName, password, url);
UsernameToken token = new UsernameToken( userName, signatureKey,
PasswordOption.SendNone );
PolicyEnforcementSecurityTokenCache.GlobalCache.Clear();
PolicyEnforcementSecurityTokenCache.GlobalCache.Add( token );
The usernameandpasswordvalues passed in
the example above are strings assigned with the appropriate values. The password argument specified in the UsernameToken constructor is the
password equivalent created using the custom hashing function shown above. This
is used to sign the message even though the password is not being sent over the
wire.
The UsernameToken is added to the PolicyEnforcementSecurityTokenCache allowing the
outgoing policy assertions to use the security token.
Step Two: Sign the Message
A password or
password equivalent is not included in the UsernameToken to provide
proof-of-possession, and so the message must be signed. One advantage of this
approach is that XML Signatures provide a more efficient means of providing
multiple security capabilities. The Subject can be authenticated and both the
origin and integrity of message content can be verified using the XML
Signature.
While using an
XML Signature as proof-of-possession rather than a password or digest provides
additional protection against unauthorized disclosure of the password, the XML
signature is still susceptible to offline cryptanalysis to obtain the signing
key. It must therefore be protected using data encryption. SSL and Message layer
protection each provide data confidentiality to protect the XML Signature from
offline cryptanalysis. If you choose to adopt message layer protection, and are
encrypting selected parts of the message, the XML Signature should be considered
a sensitive piece of message data to be encrypted.
The following
Integrity policy assertion uses the UsernameToken previously initialized
and added to the policy cache to sign outgoing messages. The policy ensures that
the signing UsernameToken cannot be
sent with a password. If the UsernameToken used to sign the message
does not have the password option set to PasswordOption.SendNone, the policy
will not be satisfied and the message will not be sent.
…
<wssp:Integrity wsp:Usage="wsp:Required">
<wssp:TokenInfo>
<SecurityToken xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext">
<wssp:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken</wssp:TokenType>
<wssp:Claims>
<wssp:UsePassword wsp:Usage="wsp:Rejected" />
</wssp:Claims>
</SecurityToken>
</wssp:TokenInfo>
<wssp:MessageParts Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
wsp:Body() wsp:Header(wsa:Action) wsp:Header(wsa:FaultTo) wsp:Header(wsa:From)
wsp:Header(wsa:MessageID) wsp:Header(wsa:RelatesTo) wsp:Header(wsa:ReplyTo)
wsp:Header(wsa:To) wse:Timestamp()
</wssp:MessageParts>
</wssp:Integrity>
</wsp:MessagePredicate>
…
Step Three: Encrypt the Message
You should encrypt the message from the requestor to the service to
provide data confidentiality, and to ensure that only the intended recipient of
the message is capable of processing it.
There are two
possible approaches to encrypt the message:
SSL encrypts the
request message from the Requestor so that only the Service can decrypt it. It
is a well-established protocol and is easy to implement on the Windows platform. It
can also provide improved performance over message layer security.
For
details on implementing SSL, see “Pattlet –Transport Layer Security using X.509
Certificates and HTTPS.”
SSL provides good performance, and is easy to configure. However,
there are circumstances when SSL is not an appropriate choice. If you need to
persist messages, or if the messages must be handled by intermediaries, then
you should use Message Layer Security. This approach
uses the server’s X.509 certificate to encrypt the request message. Unlike SSL,
sensitive parts of the message may be selectively encrypted rather than the
entire communication at the transport layer. For more information on
implementing the alternate approach to SSL with message layer X.509 security,
see Implementing Message Layer Security with X.509
Certificates in WSE 2.0.
If SSL is used
to encrypt the message, no code is required. If message layer X.509 encryption
is being used, then code must be implemented to encrypt the message. For
more information, and code examples that demonstrate how to encrypt the message,
see
Implementing Message Layer Security with X.509 Certificates in WSE 2.0.
Service Authenticates Subject and Returns a Response
This task has four steps that are recommended:
1.Decrypt
the request message
2.Obtain
the Signing Key
3.Verify
the Message Signature
4.Encrypt
the response
Each step is discussed in more detail below:
Step One: Decrypt the request
message
The option chosen to encrypt the request message determines how the
request message is decrypted by the Service. Both SSL and WSE 2.0 will decrypt
the message automatically, and require no additional coding.
Step Two: Obtain the Signing
Key
Once the message
is received by the service the information in UsernameToken is verified by WSE using
the UsernameTokenManager class.
By default, the AuthenticateToken
method of the UsernameTokenManager
class is used by WSE to validate the information in the
UsernameToken against Active Directory. However, because you are using
a custom database, and using the SendNone
option for passwords on the UsernameToken,
you
need to create a
sub-class of the UsernameTokenManager class, and then
override the AuthenticateToken method. As no password is sent, this custom manager is used to return the
password equivalent, used as the signing key to verify the XML
signature.
An entry must be
included in the Service’s web.config file under the <configuration>
element to
specify the location of the policy cache file that contains policy assertions.
Also, you should use the <securityTokenManager> element to
designate the CustomUsernameTokenManager as the
handler for incoming UsernameTokens:
<microsoft.web.services2>
<security>
<securityTokenManager
type="DirectTrustWithUsernameToken.UsernameTokenWithCustomIdentityProvider.CustomUsernameTokenManager,
DirectTrustWithUsernameToken.UsernameTokenWithCustomIdentityProvider.Service"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
qname="wsse:UsernameToken" />
</security>
<policy>
<cache
name="Configuration/PolicyCache.config" />
</policy>
</microsoft.web.services2>
You must make
sure that the identity under which the Service is running has read/execute
permissions on the policy cache file. If it does not have these permissions, the
Service will not be able to access the cache properly.
The code below
is an example of the overridden AuthenticateToken method in a CustomUsernameTokenManager
implementation. It authenticates the Subject, then creates and attaches a GenericPrincipal to the security token
so that the Service can make authorization decisions about the
Subject:
using System;
using System.Xml;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Data;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.Tokens;
namespace
DirectTrustWithUsernameToken.UsernameTokenWithCustomIdentityProvider
{
...
public class
CustomUsernameTokenManager : UsernameTokenManager
{
...
protected
override string AuthenticateToken( UsernameToken token )
{
string
password = GetUserPassword( token.Username );
if( password != null )
{
GenericIdentity identity =
new GenericIdentity( token.Username, "SQL Server" );
GenericPrincipal
principal = new GenericPrincipal( identity, null );
token.Principal = principal;
}
return password;
}
...
}
...
}
In this code
example, GetUserPassword is user
defined function that retrieves the Subject’s password stored in the Identity
Provider database.
Note: Guidance on how to communicate securely with a
database can be found at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetch12.asp
Step Three: Verify the Message
Signature
The method used
when the message was signed by the Requestor also defines the method used by the
Service to verify the signature.
You should
create a policy for the service that is used to ensure that incoming messages
are signed with a UsernameToken. The
key returned from the CustomUsernameTokenManager is used by
the policy assertion to compute the signature, and this signature is then
compared to the signature attached to the message. The following example policy
assertion requires all incoming messages to be signed with a UsernameToken. The policy is also
configured to reject requests with UsernameTokens that contain a value in
the password attribute.
<wssp:Integrity wsp:Usage="wsp:Required" >
<wssp:TokenInfo>
<SecurityToken
xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext">
<wssp:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken</wssp:TokenType>
<wssp:Claims>
<wssp:UsePassword
wsp:Usage="wsp:Rejected" />
</wssp:Claims>
</SecurityToken>
</wssp:TokenInfo>
<wssp:MessageParts
Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
wsp:Body()
wsp:Header(wsa:Action) wsp:Header(wsa:FaultTo) wsp:Header(wsa:From)
wsp:Header(wsa:MessageID) wsp:Header(wsa:RelatesTo) wsp:Header(wsa:ReplyTo)
wsp:Header(wsa:To) wse:Timestamp()
</wssp:MessageParts>
</wssp:Integrity>
<wsp:MessagePredicate wsp:Usage="wsp:Required"
Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part">
wsp:Body() wsp:Header(wsa:To)
wsp:Header(wsa:Action) wsp:Header(wsa:MessageID) wse:Timestamp()
</wsp:MessagePredicate>
Step Four: Encrypt the Response
The method used to encrypt the request message, is also used to
encrypt the response message is encrypted by the Service. Both SSL and WSE 2.0
will encrypt the response automatically, and require no additional coding.
Resulting Context
A number of benefits, liabilities, and security considerations are
associated with the implementation of UsernameToken.
Benefits
Liabilities
UsernameTokens in WSE prevent replay attacks under the covers by using a nonce and timestamp with a replay cache on the server. However, the replay cache is not shared across a server farm. Approaches to mitigate this issue include:
Security Considerations
The following subjects represent security aspects that should be considered when using this implementation of Direct Trust.
While passwords are considered one of the weakest forms of identity used for proof of possession, they are also the most common. As a result, it's important to understand threats and vulnerabilities associated with passwords.
The following formula is recommended to create the password
equivalent: SHA1( password + username + URI). The URI is unique to the service
that the requestor is accessing. This means that if the database is
compromised, the password is only good for a specific application and not all
applications that the subject uses the same password to access.
The code example shows how to implement the password hashing
formula described above:
private string GetSaltedHash( string userName, string password,
string url )
{
HashAlgorithm algorithm =
new SHA1Managed();
byte[] userNameBytes =
UTF8Encoding.UTF8.GetBytes( userName.ToLower( CultureInfo.InvariantCulture ) );
byte[] passwordBytes =
UTF8Encoding.UTF8.GetBytes( password );
byte[] urlBytes =
UTF8Encoding.UTF8.GetBytes( url.ToLower( CultureInfo.InvariantCulture ) );
byte[] value = new byte[
userNameBytes.Length + passwordBytes.Length + urlBytes.Length ];
userNameBytes.CopyTo( value,
0 );
passwordBytes.CopyTo( value,
userNameBytes.Length );
urlBytes.CopyTo( value,
userNameBytes.Length + passwordBytes.Length );
byte[] hash =
algorithm.ComputeHash( value );
return
Convert.ToBase64String( hash );
}
References
Microsoft Confidential. © 2005 Microsoft Corporation.
All rights reserved. By using or providing feedback on these materials, you
agree to the attached license agreement.