Message Authentication


The first of our three security requirements is message authenticity, and as you’ve seen, it is not wise to rely on authentication alone unless you use username and password credentials and a transport protocol with intrinsic security.

To show the problems that can occur if you use authentication as the sole means of security for a Web service, we’ll build a client application, AuthenticationClient, that interacts with a simple Web service. The user interface of the application is shown in Figure 15-4.

click to expand
Figure 15-4: The AuthenticationClient application

The AuthenticationClient application allows you to send a message to a Web service and pass along the authentication details with the message. The Web service, at http:/ /localhost/wscr/15/authenticationws.asmx, exposes one method, Echo, that accepts a message as a string and returns this message to the caller with details of the security information that it has been able to extract from the service.

The Client

It is relatively easy to add the authentication details to the message we’re sending—it takes just two lines of code. We said earlier that it’s easy to add security credentials to a message, and now you can see how easy it really is. However, we must take care of several housekeeping tasks before we add the credentials to the message.

First, as in all our WSE examples, we’ll remove the filters that we won’t use. In this case, we’ll remove the Referral, Routing, and Timestamp filters from both the input and output pipelines. You saw this code several times in the last two chapters, so we won’t repeat it here.

Although we can enter a username and password combination to use as the security credentials, we also have the option of using an X509 certificate. As you know, this provides no security whatsoever, but for completeness we’ll allow the user to add the public part of the X509 certificate to the message in this example. This also gives you the chance to look at the code for retrieving X509 certificates from the certificate store.

Retrieving Certificates from the Certificate Store

As mentioned, you can specify which folder in the certificate store you want to store the certificates. WSE provides easy access to the certificate store using static methods of the X509CertificateStore class. You can use the CurrentUserStore and LocalMachineStore methods to return an instance of the X509CertificateStore class, which you can use to access the Current User certificate store and the Local Computer certificate store, respectively. You can then use the other methods of the X509CertificateStore class to interrogate the store to retrieve the certificates you need.

This isn’t the end of the story, though. The X509 certificates are actually stored in a subfolder of the certificate store, so you must decide which part of the store you want to retrieve the certificates from. As you’ll recall from Figure 15-2, there are 10 folders you can place X509 certificates into, 9 of which are common to the Local Computer certificate store and the Current User certificate store. WSE allows you to access the 5 most useful of these store folders programmatically, by using public fields of the X509CertificateStore class (as described in Table 15-2).

Table 15-2: Public Fields of X509CertificateStore

Field

Certificate Store Folder

CAStore

Intermediate Certification Authorities

MyStore

Personal

RootStore

Trusted Root Certification Authorities

TrustStore

Enterprise Trust

UnTrustedStore

Untrusted Certificates

You can place any X509 certificates you have into any of the available folders, although it makes sense to put personal certificates in the Personal folder. It also pays to have a place to put certificates you receive from other people. Ideally, they’d go in the Other People folder, but you can’t access that programmatically. You have to use a different store. Out of the five that are available, the one that best suits our purpose in this example is the Enterprise Trust folder. As you’ll see later in the chapter, this is where we’ll put other people’s certificates that we have received.

Now it’s time to interrogate the store to retrieve the certificates that are available. The AuthenticationClient application, as you saw in Figure 15-4, allows you to select an X509 certificate from a drop-down list, which you populate by interrogating the certificate store. In the examples available in the sample code, this is done in the Load event for the main form of the application.

We first create an instance of the X509CertificateStore class and populate it with the certificates from the correct store. We do this by calling the CurrentUserStore static method on the X509CertificateStore class and specifying which part of the store we want. In this case, we want the Personal part of the store, so we specify a value of X509CertificateStore.MyStore. We then call the OpenRead method to open the store for read- only access:

// specify and open the correct certificate store X509CertificateStore certStore = X509CertificateStore.CurrentUserStore     (X509CertificateStore.MyStore); certStore.OpenRead();

Now that we have an open reference to the correct certificate store folder, we can interrogate the store. The X509CertificateStore class has four methods you can use to search for a certificate, but in this case we want to retrieve all of the available certificates, so we’ll use the Certificates property. This returns a collection of X509Certificate objects that correspond, not surprisingly, to the certificates that are available. We can iterate through the collection of certificates and add these to the list box on the form:

// loop through the collection and get the certificates foreach(X509Certificate certCertificate in certStore.Certificates) {     // add the certificate to the combo box     cboX509.Items.Add(X509.GetCommonName(certCertificate.GetName())); }

As you’ll see from the Add method of the Items collection of the ComboBox object, we first call a helper function to give us the common name of the certificate. The GetName method of the X509 class returns the complete X500 string for the certificate; we’re interested only in the Issued To part. The GetCommonName method of the helper class simply manipulates the string and retrieves the part we’re interested in.

We then close the open connection to the certificate store and, if we’ve populated the list, automatically select the first entry:

// close the open store certStore.Close(); // select the first entry if(cboX509.Items.Count != 0) cboX509.SelectedIndex = 0;

Now that the ComboBox object is populated, we can get to the really important code—the code that adds the security credentials to the outgoing message.

Adding the SecurityToken to the Message

Once the application has launched, the user can enter the message that the server is to echo back. The security credentials must also be selected—either a username and password combination or an X509 certificate from the list box. Clicking the Echo button executes the click handler for the button.

Within the click handler is an enclosing try/catch loop that traps any exceptions raised by the code and displays the exception details to the user. In reality, we’d need more complex error handling to provide a better user experience, but for now we’re only interested in the code that’s directly related to security.

The first thing we do is create the two objects we’ll need for the rest of the click handler. The first object is the Web service proxy object we’ve come to expect. We’ll look at the Web service for this example shortly; here we’re interested in the second object, secClientToken.

The secClientToken object is an instance of the SecurityToken class. As you saw earlier, this is a base class for several derived classes; we can use it to hold instances of any of the derived classes. This allows us to use the same code to add any type of security credential—WSE takes care of retrieving the correct parts of the credential for the action that it is currently performing:

// create the proxy class AuthenticationWSWse proxy = new AuthenticationWSWse(); // declare the token that represents the client SecurityToken secClientToken;

We then check to see which type of security credential we’re providing, by using the status of the option buttons on the form.

If you’re providing username and password credentials, you create a new instance of the UsernameToken class and specify the username and password to the constructor. You also tell the constructor what you want done with the password you’re sending— you can pass no password, a plain-text password, or a hashed password (as shown here).

// create the username token secClientToken = new UsernameToken(txtUsername.Text, txtPassword.Text,     PasswordOption.SendHashed);

If you’re using an X509 certificate, things are a little more complex because you must first retrieve the certificate you want to use from the Certificate Store. We added only the certificate name to the list box, not an instance of the certificate, so we need to retrieve the certificate from the store again.

To retrieve the correct certificate from the store, we use the FindCertificateBySubjectString method of the X509CertificateStore class. This method performs a search, using the text we specify, on all the certificates in the open certificate store folder. We pass to this method the text of the currently selected item in the list box and the method returns a collection of certificates that contain the text we’re searching for. If several certificates have similar names, the collection might contain several different entities and more checking might be needed to ensure that we’ve retrieved the correct certificate. In this case, we’ll assume that the first certificate in the collection is the one that we require:

// get the certificate we selected from the store X509Certificate certCertificate = certStore.FindCertificateBySubjectString     (cboX509.Text)[0];

Once the certificate has been retrieved, we can use it to construct a new X509SecurityToken by passing the X509Certificate to the X509SecurityToken constructor. After creating the security token, we close the connection to the certificate store:

// create the X509 token secClientToken = new X509SecurityToken(certCertificate); // close the certificate store certStore.Close();

Whichever path the code took through the code, we now have a valid SecurityToken object that we add to the Tokens collection of the Security property of the request’s SoapContext object. Then we make the call to the Web service, passing the message we want echoed:

// add the token to the message proxy.RequestSoapContext.Security.Tokens.Add(secClientToken); // make the call MessageBox.Show(proxy.Echo(txtMessage.Text), "Returned message");

As the message is passed through the output pipeline, the Security output filter takes the contents of the Security.Tokens collection of the request’s SoapContext object and adds them as <wsse:UsernameToken> or <wsse:BinarySecurityToken> elements to the <wsse:Security> security SOAP header (as you saw in Chapter 12).

The Web Service

On arriving at the Web service, the Security input filter extracts any <wsse:UsernameToken> and <wsse:BinarySecurityToken> elements from the <wsse:Security> header and reconstructs the Security.Tokens collection in the request’s SoapContext object. We can then access this to retrieve the security credentials of the sender.

The first thing the Web service does is create a variable to hold the message that we’ll return to the client and a variable to hold the request’s SoapContext. We then check, as we do with all of our WSE-enabled code, that the request has a SoapContext:

string strResponse = ""; // get the request context SoapContext reqContext = HttpSoapContext.RequestContext; // must be a WSE message  if (reqContext == null)     throw new ApplicationException("No WSE details discovered");

We then check that we’ve actually received authentication details in the message by checking that the Security.Tokens collection isn’t empty. If it is, we don’t have the details of any tokens to add to the message, and we return a message telling the user that we didn’t receive any authentication details and echo the message back to the sender.

If we do have authentication information, we iterate through the collection of security tokens and extract the correct details. We cast the base SecurityToken to the correct type and extract the details we need from it.

For the UsernameToken object, we simply want to get the Username property, but for the X509SecurityToken object, we want the common name of the certificate, so we pass the results of the GetName method to the X509.GetCommonName helper method:

// we've got a message with authentication strResponse = "The following message had authentication details from "; // add the details to the message foreach(SecurityToken secToken in reqContext.Security.Tokens) {     if (secToken is UsernameToken)     {         strResponse = strResponse + ((UsernameToken)secToken).Username;     }     if (secToken is X509SecurityToken)     {         strResponse = strResponse + X509.GetCommonName             (((X509SecurityToken)secToken).Certificate.GetName());     } }

The received message is then added to the message we generated and is returned to the sender:

// now add the incoming message to the response strResponse = strResponse + strMessage; // now return the response to the caller return (strResponse);

Verifying Username and Password Credentials

The one thing we haven’t discussed is how the authentication details are validated by the Web service. If you’re using X509 certificates as the credentials, no checking is done by the Security input filter to validate the certificate you passed—another nail in the coffin of using X509 certificates for authentication!

If you’re using username and password security credentials, once you receive a <wsse:UsernameToken>, you must verify that the username and password are correct. If you don’t pass a password, no authentication check is made and the user is assumed to be authentic.

To authenticate the username and password combinations received at the Web service, you must provide an implementation of the IPasswordProvider interface—if you don’t, any requests that contain username and password credentials will be rejected and an exception of type ConfigurationException will be thrown. The IPasswordProvider interface has one method, GetPassword, that accepts the username that is being authenticated and requires the password to be returned. WSE then accepts or rejects the security credentials by comparing the password that was received in the message to the password returned from the GetPassword method.

The GetPassword method can be as complex or as simple as is required by the application. In our example Web service, we have only two valid user accounts and a simple switch/case statement that returns a hardcoded password:

public class authPassword : IPasswordProvider {     public string GetPassword(UsernameToken token)     {         switch (token.Username)         {             case "user1":                 return ("pass1");             case "user2":                 return ("pass2");             default:                 return (null);         }     } }

We must inform the Security input filter of the details for our implementation of the IPasswordProvider interface. We do this in the configuration file:

<microsoft.web.services>   <security>     <passwordProvider type="_15.authPassword, 15" />   </security> </microsoft.web.services>

Once we configure WSE to use our implementation of IPasswordProvider, all requests to the Web service that have username and password credentials attached will cause the authPassword class to be instantiated and the GetPassword method to be called.

Note

As you’ve probably guessed, the GetPassword method presents us with a problem. We’re most likely to store the passwords for a user in one-way encrypted form—be it in Active Directory, an SQL Server database, or any of a number of other locations. The GetPassword method must return the plain-text password so WSE can compare it to the password that is received. We have only one solution at present, and that is to store the passwords in plain text—this is possible in an SQL Server database but is generally not recommended.

Security Problems

As we pointed out earlier, you encounter several security problems when you use authentication as the sole means of security for your application. You’ve seen that using username and password credentials requires you to have a pretty insecure method of storing the passwords at the server, and unless you’re using a secure transport protocol, the messages you send can be intercepted and modified relatively easily.

If you’re using a secure transport protocol such as HTTPS, the message you send can use username and password security credentials, and you can be sure that your password will be kept secure—well, as secure as your credit card number was the last time you made an online purchase.

You already know that when you use X509 certificates for authentication they don’t provide any security whatsoever because you’re using the public part of the certificate, which is open to anyone who wants it.

The sample code for this chapter contains a public certificate for a fictitious user, Anne Bloggs, at http://localhost/wscr/15/annebloggs.cer. Anne has made her public certificate freely available so you can encrypt messages to her if you need to. If an unscrupulous person receives Anne’s public certificate, he can quite easily spoof her identity if a Web service relies purely on authentication using X509 certificates. Go ahead and add the public certificate to the Personal folder of the Current User certificate store, and you’ll see that you can use this as the security credentials of the Web service. Not very secure at all!

If you want to use X509 certificates, you must force the signing of the message and check for this when the message is received. As you’ll see shortly, this approach doesn’t involve much more work, but it is a lot more secure because it relies on the private part of the X509 certificate, which is hopefully still private.




Programming Microsoft. NET XML Web Services
Programming MicrosoftВ® .NET XML Web Services (Pro-Developer)
ISBN: 0735619123
EAN: 2147483647
Year: 2005
Pages: 172

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