>
 Saturday, June 11, 2005
« ASP.NET 2.0 Migration Help with the Memb... | Main | SDC, Netherlands: The Quest for Scalabil... »

Definition: API

Application Programming Interface

 

What I find really annoying sometimes are the choices made that restrict my ability to extend or directly call specific parts of a programming interface. Keyword: PROGRAMMING. It baffles me when I see some of the methods that were marked “internal”, and fields marked “private” when the class has overridable methods that I might actually WANT to override or call directly, and fields I might require access to from a subclass!!!!

 

When I recently put together some samples that used the MembershipRoles API for 1.1, I was frustrated by a few very specific such limitations, that to me seem like flaws in the API design since they are obvious areas that might require override.

 

The sample in question can be found here:

WSESecurityDemo_MemberRoles.zip (44.16 KB)

 

The sample uses the MembershipRoles API from a WSE 2.0 service to handle UsernameToken authentication. The problem: hashed passwords. Passwords should ALWAYS be hashed in any database, full stop. When a UsernameToken is sent in a WS-Security header to the service, the WSE plumbing will ask my UsernameTokenManager implementation to authenticate the user against the custom database, and return the password to WSE for comparison against the token. Since WSE is responsible for the comparison, and the password is hashed in the database, we have two choices:

a)      tell our clients to hash their passwords the same way before sending the UsernameToken message

b)      tell our clients to send passwords as clear text (encrypted of course in some other way) and we handle it on the server side

 

My opinion is always to reduce the overhead of what the client must do, and encapsulate this functionality on the server side (option b). In this way, you can later modify your hashing algorithms for example, without impacting your existing clients. This is also more secure since we don’t really want to share our hashing algorithms and process with other parties.

 

So, WSE sends a clear text password, and we have to find a way to tell WSE to compare our hashed database password with the hashed version of the UsernameToken password. This implies we must somehow hook in to the process of password validation, to supply a hashed version of the token to the WSE plumbing.

 

We can do this by overriding the VerifyPlainTextPassword() method on the UsernameTokenManager class:

 

using Microsoft.Web.Services2.Security.Tokens;

 

protected override void VerifyPlainTextPassword(UsernameToken token, string authenticatedPassword)

{

 

SqlMembershipProviderEx provider = Membership.Provider as SqlMembershipProviderEx;

string salt;

     string password = provider.GetPasswordWithFormatFromDBEx(token.Username, out salt);

 

string hashedpw = provider.EncodePasswordEx(token.Password,salt);

 

UsernameToken newtoken = new UsernameToken(token.Username, hashedpw,PasswordOption.SendPlainText);

 

base.VerifyPlainTextPassword (newtoken, authenticatedPassword);

}

 

So far so good? Well, you’ll notice that there are a few calls to SqlMembershipProviderEx above. This is a subclass to the SqlMembershipProvider that I created to handle a few things. First, I needed to get access to the hashed password in the database. The problem is you can’t configure the provider to support access to passwords AND hash them. Strange, I can retrieve a plain text password no problem, but once hashed, I am restricted from doing so??? I really don’t get this…I think it should be configurable. Let me (uh, the developer of the application following through on security implementation) decide what is ok, or not.

 

Since XML configuration didn’t let me do it, I thought I might override the class and set the property myself. But, it is private, instead of protected allowing subclasses access:

 

private bool _EnablePasswordReset;

 

Then I thought I might call the function directly that accesses the database, but that relies on the _EnablePasswordReset property, so that won’t work either. That is what led to my creating a copy of the function to access the database. Lame, but necessary.

 

public string GetPasswordWithFormatFromDBEx(string username, out string text2)

{

     SqlConnection connection = null;

     SqlDataReader reader1 = null;

 

     text2=null;

 

     try

     {

SqlParameter parameter1 = null;

 

     connection = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["wsha"]);

     connection.Open();

 

     SqlCommand command1 = new SqlCommand("dbo.aspnet_Membership_GetPasswordWithFormat", connection);

     command1.CommandType = CommandType.StoredProcedure;

 

                   command1.Parameters.Add(this.CreateInputParamEx("@ApplicationName", SqlDbType.NVarChar, this.ApplicationName));

                   command1.Parameters.Add(this.CreateInputParamEx("@UserName", SqlDbType.NVarChar, username));

     parameter1 = new SqlParameter("@ReturnValue", SqlDbType.Int);

     parameter1.Direction = ParameterDirection.ReturnValue;

     command1.Parameters.Add(parameter1);

 

     reader1 = command1.ExecuteReader(CommandBehavior.SingleRow);

 

     string text1 = null;

     if (reader1.Read())

     {

          text1 = reader1.GetString(0);

          text2 = reader1.GetString(2);

          return text1;

     }

     text1 = null;

     return text1;

}

     catch

     {

          throw;

     }

     finally

     {

          if (reader1 != null)

          {

              reader1.Close();

              reader1 = null;

          }

          if (connection != null)

          {

              connection.Close();

               connection = null;

          }

     }

}

 

Next up, I needed to create a copy of the UsernameToken with a hashed version of the password send by the client:

 

string hashedpw = provider.EncodePasswordEx(token.Password,salt);

 

UsernameToken newtoken = new UsernameToken(token.Username, hashedpw,PasswordOption.SendPlainText);

 

This also required me to create a function that hashed passwords the same way as the provider.

 

public string EncodePasswordEx(string pass, string salt)

{

     byte[] buffer1 = Encoding.Unicode.GetBytes(pass);

     byte[] buffer2 = Convert.FromBase64String(salt);

     byte[] buffer3 = new byte[buffer2.Length + buffer1.Length];

     byte[] buffer4 = null;

     Buffer.BlockCopy(buffer2, 0, buffer3, 0, buffer2.Length);

     Buffer.BlockCopy(buffer1, 0, buffer3, buffer2.Length, buffer1.Length);

 

             

     HashAlgorithm algorithm1 = HashAlgorithm.Create("SHA1");

     if (algorithm1 == null)

     {

          throw new Exception("Error creating hash algorithm type: SHA1");

     }

     buffer4 = algorithm1.ComputeHash(buffer3);

    

     return Convert.ToBase64String(buffer4);

}

 

Both of these overridden functions are basically hacks, because I hard code the connection string to use in the first function and I hard code the hash algorithm in the second. I should be reading the XML configuration to gather the provider settings, as does the API, but of course that code is private too.

 

Is there a moral to the story?

When you design an API, and you really want programmers to PROGRAM their applications according to their own needs…think about what methods and fields you make private or internal. Be nice.

6/11/2005 7:13 PM Web Services | WSE  | Comments [1]  |  View reactions  |  Trackback
    ON THIS PAGE
    SEARCH
    CATEGORIES
    ARCHIVES
    BLOGROLL

Designed by NUKEATION STUDIOS