Using WSE to Secure a Web Service


If you want to get started securing your .NET Web service today, WSE can help you take a big step forward toward implementing WS-Security and related Web services security specifications. WSE supports the following security features:

  • Username tokens

  • Binary security tokens based on X.509 certificates

  • Binary security tokens based on Kerberos tickets

  • Custom binary security tokens

  • Custom XML security tokens

  • Digital signatures

  • XML encryption

  • Defining security policies

  • Issuing security tokens

  • Verifying trust

  • Creating security contexts

WSE provides support for publishing metadata that describes the policies associated with accessing your Web service (WS-Policy and WS-SecurityPolicy), establishing secure conversations (WS-SecureConversation), and establishing trust relationships for secure Web services communication (WS-Trust). These final four capabilities will be discussed in later chapters.

When you enable WSE for your Web service, the WSE security input filter looks for a Security header in each incoming SOAP requests to the service. If this header is present, WSE converts any child elements in the Security element into WSE programming objects. Likewise, the WSE security output filter converts WSE programming objects into XML security elements for out-bound messages. For added security, when an incoming message contains security token elements in the Security header, the principle specified in the token must be positively authenticated and authorized; otherwise , the message will be immediately rejected. To make things even more secure, you can define a policy for WSE to enforce the requirement that all incoming messages have the desired security tokens as well as proper digital signatures and encryption. I will discuss WSE support for WS-Policy in Chapter 6.

Handling Security Tokens

To add a security token to an outbound message using WSE, all that you need to do is to programmatically define the token using an instance of an appropriate class that overrides the SecurityToken base class then add this object to the SoapContext of the outbound message. When received by a WSE-enabled application, the WSE runtime generates security token objects based on contents of the Security header in the incoming message. These tokens, which belong to the SoapContext object for the incoming message, are then presented to the appropriate security token manager for authentication and authorization. These security token objects are accessible to your application for as long as the SoapContext object exists.

Next, I will demonstrate how to use the WSE API to manually attach security tokens to outgoing SOAP messages. This process can also be automated so that the WSE runtime adds the required tokens for you. In order to do this, you must configure a policy for outbound messages and define a security token cache that WSE can use to get a valid token. I will show how to do this in Chapter 6.

Attaching a Username Token

Attaching a username token to a request message is as simple as defining a new UsernameToken object and attaching it to the SoapContext for the outbound message, as in the following example request to a Web service:

 // Instantiate the Web service proxy 
DocumentService myService = new DocumentService();

// Get the SoapContext of the request message
SoapContext myContext = myService.RequestSoapContext;

// Define a username token to attach to the message
UsernameToken myNameToken = new UsernameToken(myName, myPwd,
PasswordOption.SendHashed);

try
{
// Add the new token to the Tokens collection in the Security object
// in the SoapContext of the request message
myContext.Security.Tokens.Add(myNameToken);

// Call the GetDocument method on the Web service
docNames = myService.GetDocument(docNames);
}
catch(Exception ex)
{
// Do any error handling here
}

Based on this code, ASP.NET generates the following SOAP message, which includes the WSE-generated Security element in the message header.

 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:wsu="http://schemas.xmlsoap.org/ ws/2002/07/utility" xmlns:wsse="http:// schemas.xmlsoap.org/ws/2002/12/secext"> 
<soap:Header>

<wsu:Timestamp>
<wsu:Created>2003-07-31T21:18:11Z</wsu:Created>
<wsu:Expires>2003-07-31T21:23:11Z</wsu:Expires>
</wsu:Timestamp>
<wsse:Security soap:mustUnderstand="1">
<wsse:UsernameToken
xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
wsu:Id="SecurityToken-e7662bf0-574a-4371-ba6e-1e6d8cf3c0bc">
<wsse:Username>Jeannine Gailey</wsse:Username>
<wsse:Password Type="wsse:PasswordDigest"
>Ak9WkiNBNOxA3Oamj3cLMWK3m78=</wsse:Password>
<wsse:Nonce>Tt5XVq9dvUk47Dc1kDlsdw==</wsse:Nonce>
<wsu:Created>2003-07-31T21:18:10Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<GetDocument xmlns="http://example.com/documentservice">
<documentID>
<int>1</int>
<int>2</int>
</documentID>
</GetDocument>
</soap:Body>
</soap:Envelope>

Because the code example specifies sending a hashed password, the resulting Password element contains cryptographic hash data. As specified in the WS-Security Addendum specification, this hash is actually computed using a combination of the password, the creation date and time, and a nonce, which is a temporary random string value. The creation date and time and nonce value are included in the message. Also WSE automatically adds a Timestamp element with the required TTL of less than 5 minutes.

Attaching a Binary Security Token Using an X.509 Certificate

You must follow a similar process when attaching a binary security token, such as a token containing an X.509 certificate, to an outgoing message, except in this case you must use an instance of a class that is derived from the BinarySecurityToken class. In the following example call to the GetDocument Web method, the public portion of an X.509 certificate is attached using an X509SecurityToken object:

 // Instantiate the Web service proxy 
DocumentService myService = new DocumentService();

// Get the SoapContext for the request message
SoapContext myContext = myService.RequestSoapContext;

// Instantiate a new binary security token for the
// X.509 certificate used to sign the message
X509SecurityToken myToken;

// Set the key bytes based on the supplied key string
byte[] keyIdentifer = Convert.FromBase64String(keyString);

// Open and read the current user certificate store
X509CertificateStore myStore =
X509CertificateStore.CurrentUserStore(X509CertificateStore.MyStore);
myStore.OpenRead();

// Get the certificate that matches the supplied key
X509CertificateCollection myCerts =
myStore.FindCertificateByKeyIdentifier(keyIdentifer);

// Declare a new certificate
X509Certificate myCert = null;

// If the collection is not empty, get the first
// certificate in the collection
if (myCerts.Count == 1)
{
// Use the returned certificate
myCert = myCerts[0];

// Define the security token based on the certificate.
myToken = new X509SecurityToken(myCert);
}

if (myToken != null)
{
// Add the new token to the Tokens collection in the Security object
// in the SoapContext of the request message
myContext.Security.Tokens.Add(myToken);

try
{
// Call the GetDocument method on the Web service
docNames = myService.GetDocument(docNames);
}
catch(Exception ex)
{
// Do error handling here
}
else
{
throw new ApplicationException("A security token cannot be found");
}

This directs ASP.NET and the WSE runtime to generate the following SOAP request message:

 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"  xmlns:wsu="http://schemas.xmlsoap.org/ ws/2002/07/utility" xmlns:wsse="http:// schemas.xmlsoap.org/ws/2002/12/secext"> 
<soap:Header>

<wsu:Timestamp>
<wsu:Created>2003-08-01T04:33:24Z</wsu:Created>
<wsu:Expires>2003-08-01T04:38:24Z</wsu:Expires>
</wsu:Timestamp>
<wsse:Security soap:mustUnderstand="1">
<wsse:BinarySecurityToken ValueType="wsse:X509v3"
EncodingType="wsse:Base64Binary"
xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
wsu:Id="SecurityToken-d98ed546-1f30-4654-971d-19de53be9ab4"
>MIIFPzCCBCegAwIBAgIKZ84w0BAQUFADBw<;$VE></wsse:BinarySecurityToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<GetDocument xmlns="http://example.com/documentservice">
<documentID>
<int>1</int>
<int>2</int>
</documentID>
</GetDocument>
</soap:Body>
</soap:Envelope>

Note that since the Base64Binary representation of a binary X.509 certificate is fairly large (1,796 bytes in this example), I have truncated this data for readability. To enable an attached security token to be used for message signing and encryption, WSE assigns a GUID to each token.

Implementing a Security Token Manager

Security tokens are used by WSE to both authenticate and authorize requests. Since all security tokens must be positively authenticated to gain access to a WSE- enabled Web service, you must write a security token manager to handle this authentication and authorization process. Out of the box, WSE implements a username token manager that attempts to validate incoming requests against Windows Domain accounts. To implement a custom authentication scheme with username tokens, you must write your own username token manager and override the AuthenticateToken method with your own method that provides the corresponding password for the supplied username. WSE hashes this password returned by AuthenticateToken with the nonce and timestamp value from the token and compares the resulting hash with the hash Password value of the UsernameToken . If the values match, then the token s Principle property is set to the username, which can later be used within the Web service to check authorizations against predefined roles using the Principle.IsInRole method.

The following example class overrides UsernameTokenManager and is used by WSE to validate an incoming UsernameToken against a custom authentication list:

 [SecurityPermission(SecurityAction.Demand, 
Flags= SecurityPermissionFlag.UnmanagedCode)]
public class CustomUsernameTokenManager : UsernameTokenManager
{
protected override string AuthenticateToken(UsernameToken myNameToken)
{
// Ensure the SOAP message sender passed a non-null UsernameToken
if (myNameToken == null) throw new ArgumentNullException();

// Call a method that gets the local copy of the password
// for the supplied username from a secure location
string myPwd = GetPassword(myNameToken.Username);
return myPwd;
}
}

Once this manager is created, WSE must be configured to use this new manager instead of the default manager. The easiest way to do this is to use the WSE Settings Tool. To add a custom security token manager for the service:

  1. In Visual Studio .NET, right-click the Web service project and select WSE Settings 2.0.

  2. Select the Security tab, as in Figure 5-3.

    click to expand
    Figure 5-3: Security settings tab in the WSE Settings Tool

  3. Under Security Tokens Managers, click Add. This displays the SecurityToken Manager dialog box shown in Figure 5-4.

    click to expand
    Figure 5-4: SecurityToken Manager dialog box

  4. In this dialog box, specify the fully-qualified name of the class implementing the handler and the name of the assembly containing the class, without extension, in the Type field.

  5. Add the WS-Security namespace ( http://schemas.xmlsoap.org/ws/2002/12/secext ) in the Namespace field.

  6. Since we are registering a Username token manager, enter wsse:UsernameToken in the QName field. Based on these selections, the WSE Settings Tool adds the following section to the Web service s Web.config file:

     <microsoft.web.services> 
    <security>
    <SecurityTokenManager
    xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext"
    qname="wsse:UsernameToken"
    type="XmlServices.CustomUsernameTokenManager, DocumentService" />
    </security>
    </microsoft.web.services>

You could, of course, just add this to the configuration file manually. In this configuration file, the type of the SecurityTokenManager is comprised of the namespace and class implementing the handler and the name of the assembly containing the class, without extension. Of course, if you don t like editing the Web.config file, you can supply this same information in the Security Token Managers section on the WSE Settings tool s Security tab.

In this sample, I set the SecurityPermissionFlag to UnmanagedCode as recommended. Setting this flag should help to reduce the potential of an attack against this class because the ability to call unmanaged code is generally only granted to very trusted assemblies, such as the WSE assembly. It is likely that an attacker will not have these permissions. Passwords should always be stored securely, which means a hash of the password should be stored rather than the password itself. When you are authenticating username tokens against hashed passwords, a password being added to the UsernameToken must first be hashed using the same algorithm used to hash passwords stored at the server.

Signing a SOAP Message

WSE enables you to add signatures to outgoing messages. Signatures are generated using a specified security token object. All security token types supported by WSE can be used to sign a SOAP message; however, not all instances of a token object support signing. You can verify that a token supports signing using the token s SupportsDigitalSignature property. The following example uses an X509-based token to sign an outgoing request message:

 // Instantiate the Web service proxy 
DocumentServiceWse myService = new DocumentServiceWse();

// Get the SoapContext for the request message
SoapContext myReqContext = myService.RequestSoapContext;

// Get the X509-token to sign the request
X509SecurityToken myToken = GetX509Token(clientPrivateKey);

if (myToken != null)
{
// Add the new token to the Tokens collection in the Security object
// in the SoapContext of the request message
myReqContext.Security.Tokens.Add(myToken);

// Verify that the token can be used for signing
if (myToken.SupportsDigitalSignature)
{
// Sign the message with the security token
myReqContext.Security.Elements.Add(new Signature(myToken));

}
else
{
throw new ApplicationException("You cannot use this token to access "
+ "the service please select another authentication method");
}
try
{
// call the GetDocument method on the Web service
docNames = myService.GetDocument(docNames);

catch(Exception ex)
{
// Do error handling here
}

}

This code directs ASP.NET and the WSE runtime to generate the following SOAP request message:

 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext">
<soap:Header>

<wsu:Timestamp>
<wsu:Created wsu:Id="Id-2c4293be-4c24-461e-94b0-2205055ef71b"
>2003-09-12T00:20:03Z</wsu:Created>
<wsu:Expires wsu:Id="Id-9f5b94d8-ea3d-4912-bc46-dcbb7dbaf92f"
>2003-09-12T00:25:03Z</wsu:Expires>
</wsu:Timestamp>
<wsse:Security soap:mustUnderstand="1">
<wsse:BinarySecurityToken ValueType="wsse:X509v3"
EncodingType="wsse:Base64Binary"
xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
wsu:Id="SecurityToken-9c3a8620-8074-4b5b-8d9b-235bb8e24a8b"
>MIIFFDCCA/ygAwIBAgIKYbb2Z0MRQwEgY<;$VE>=</wsse:BinarySecurityToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#Id-53453756-e297-4645-8dba-0af0afe4a0d8">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>sLyTgCujxZ8cndLleWWW9jike14=</DigestValue>
</Reference>

</SignedInfo>
<SignatureValue>vulvHsRnGSobcDklJZQE883oY9Ep<;$VE>=</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference
URI="#SecurityToken-9c3a8620-8074-4b5b-8d9b-235bb8e24a8b" />
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
</soap:Header
<soap:Body wsu:Id="Id-53453756-e297-4645-8dba-0af0afe4a0d8">
<GetDocument xmlns="http://example.com/documentservice">
<documentID>
<int>1</int>
<int>2</int>
</documentID>
</GetDocument>
</soap:Body>
</soap:Envelope>

When this message is received, WSE constructs a Signature object that can be used by the username security token manager to compare against a signature using the local copy of the password, timestamp, and nonce.

Encrypting a SOAP Message

Like digital signatures, WSE uses security tokens to encrypt the contents of a SOAP message, provided that the token supports encryption. For example, in PKI, a token containing the sender s private key cannot be used for encryption because the message could be easily decrypted using a public key. Also, username tokens do not support encryption. The following example uses a token based on an X.509 certificate to encrypt and sign the body of a SOAP request message:

 // Instantiate the Web service proxy 
DocumentServiceWse myService = new DocumentServiceWse();

// Get the SoapContext for the request message
SoapContext myReqContext = myService.RequestSoapContext;

// If we have a token, use it for signing.
if (myTokens[0] != null)
{
// Add the new token to the Tokens collection in the Security object
// in the SoapContext of the request message
myReqContext.Security.Tokens.Add(myTokens[0]);

// Verify that the token can be used for signing
if (myTokens[0].SupportsDigitalSignature)
{
// Sign the message with the security token
myReqContext.Security.Elements.Add(new Signature(myTokens[0]));

// If we have a security token with a public key,
// verify that it supports encryption
if (myTokens[1] != null && myTokens[1].SupportsDataEncryption)
{
// Encrypt the message body using this security token
myReqContext.Security.Elements.Add(new EncryptedData(myTokens[1]));
}
}
else
{

}

try
{
// call the GetDocument method on the Web service
docNames = myService.GetDocument(docNames);


}
catch(Exception ex)
{
// Do error handling here
}
}

In this example, the myTokens array contains an X.509 security token, derived from the certificate containing the client s private key, that is used for both authentication and signing the request message, as well as a second
X.509-based token, derived from the certificate containing the service s public key, that is used to encrypt the request message. In this way, the recipient can use the sender s public key, from the attached token, to verify the signature, and its own private key to decrypt the message. Based on this code, ASP.NET and WSE generate the following SOAP message:

 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext">
<soap:Header>

<wsu:Timestamp>
<wsu:Created wsu:Id="Id-2c4293be-4c24-461e-94b0-2205055ef71b"
>2003-09-12T00:20:03Z</wsu:Created>
<wsu:Expires wsu:Id="Id-9f5b94d8-ea3d-4912-bc46-dcbb7dbaf92f"
>2003-09-12T00:25:03Z</wsu:Expires>
</wsu:Timestamp>
<wsse:Security soap:mustUnderstand="1">
<wsse:BinarySecurityToken ValueType="wsse:X509v3"
EncodingType="wsse:Base64Binary"
xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
wsu:Id="SecurityToken-9c3a8620-8074-4b5b-8d9b-235bb8e24a8b"
>MIIFFDCCA/ygAwIBAgIKYb7RQUFADBwMR<;$VE>=</wsse:BinarySecurityToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#Id-53453756-e297-4645-8dba-0af0afe4a0d8">
<Transforms>
<Transform
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>sLyTgCujxZ8cndLleWWW9jike14=</DigestValue>
</Reference>

</SignedInfo>
<SignatureValue>vulvHsRnGSoDkJZQE883oY9Eprl7<;$VE>=</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference
URI="#SecurityToken-9c3a8620-8074-4b5b-8d9b-235bb8e24a8b" />
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
</soap:Header>
<soap:Body wsu:Id="Id-53453756-e297-4645-8dba-0af0afe4a0d8">
<xenc:EncryptedData
Id="EncryptedContent-26203da3-5837-4938-966f-42bf357caaf7"
Type="http://www.w3.org/2001/04/xmlenc#Content"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" />
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<wsse:Reference
URI="#SecurityToken-0fa275ad-234c-4bae-98ef-154174bcec39" />
</wsse:SecurityTokenReference>
</KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>OGUDG6dm0FeRR0XEhbTke0n<;$VE>==</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soap:Body>
</soap:Envelope>

Using X.509 certificates can become a little bit challenging outside of a production environment, particularly when it comes to obtaining and installing valid test certificates that can be used for signing and encryption. There are known performance issues when using test certificates generated using the makecert tool, such as the ones that are included with WSE. For optimal performance, you should get certificates from a certificate authority. Fortunately, WSE supports the use of certificates issued by a test root CA and allows you to request that it not verify trust during testing. These security configuration options can be set on the Security tab of the WSE Settings tool, shown back in Figure 5-3. In addition, WSE includes a certificate tool that allows you to view properties of certificates in your certificate stores, including determining key string values. Also, the ASP.NET user account needs to have access to the key files, which can be granted using the WSE certificate tool. For more information, see the WSE SDK documentation or the Certificate Creation Tool (Makecert.exe) documentation in the .NET Framework documentation.




Understanding Web Services Specifications and the WSE
Understanding Web Services Specifications and the WSE (Pro Developer)
ISBN: 0735619131
EAN: 2147483647
Year: 2006
Pages: 79

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net