Smart Card Authentication Module Update Released

3:14 PM j. montgomery 42 Comments

I’ve finally wrapped up updating the SmartCardAuthenticationModule. The link to the download is at the end of this post.

A complete write-up of the previous version can be located here: http://securitythroughabsurdity.com/2007/04/implementing-smartcard-authentication.html

Changes / Improvements

  • * Added support for ASP.NET Membership which means support for Profiles and Roles as well.
  • * Removed all custom database requirements from the Module. If custom DB access is needed this can be implemented in a Global event.
  • * Removed SmartCardPrincipal class. Smart Cards only help establish identity and don’t provide any roles membership information so I opted to remove the class and instead just wrap the identity into a GenericPrincipal. If the ASP.NET Role provider is being used, the Role module will automatically wrap the SmartCardIdentity in a RolePrincipal. Implementer's can also add custom event code in the Global to use any Principal of their choice.
  • * Added ASP.NET Health Monitoring events for auditing Success and Failed logins, as well as when Membership accounts are created.
  • * Added support for custom error pages on a 401 Unauthorized.
  • * Added the following Smart Card Authentication Module events:
    •     * Authenticate
    •     * FailedMembershipAuthentication
    •     * MembershipValidating
    •     * MembershipUserCreated
    •     * MembershipUserCreating

OOTB Behavior

The out of the box behavior for the Smart Card Authentication module is as follows:
  1. 1. With ASP.NET Membership – The first time a user visits the web site, the Smart Card Authentication Module will automatically create a Membership account in a disabled state. The new MembershipUser will not have access until the account is enabled through the Membership Admin. For users who visit the site have a Membership account. the Module will call the Membership.Validate() method and will only allow them access if their Smart Card is the same as it was when they enrolled and the account is enabled.
  2. 2. With ASP.NET Membership and ASP.NET Roles – The RolePrincipal will contain the SmartCardIdentity. IsInRole() checks will work as expected and the SmartCardIdentity will also be available.
  3. 3. Without ASP.NET Membership/RoleProvider enabled – the SmartCardAuthenticationModule will authenticate the user and attach a GenericPrincipal with NO ROLES to the HttpContext.User. To provide custom roles (when not using the ASP.NET RoleProvider), subscribe to the SmartCardAuthentication_Authenticate event in Global.asax and attach an IPrincipal containing the roles appropriate for authorization.

Installation

Configure the Web project to have a reference to the SmartCardAuthenticationModule. This can be accomplished in one of two ways:.
  1. 1. Add a reference to the SmartCardAuthentication.dll to the web application project
  2. 2. To have Smart Card Authentication Module source available in the solution, add the SmartCardAuthentication Project to the Solution containing your web project and then add a project reference to the SmartCardAuthenticationModule.
In IIS, install a SSL/TLS Certificate and for Client Certificates, make sure to check either Accept or Require for the Web Site or Application. For production environments, Require SSL checked and Require Client Certificates selected is recommended:
ProdSSLSettings

Additionally, make sure to Enable only Anonymous Authentication in IIS:
IisAuthentication

HTTP Modules are installed differently depending on which version of IIS being used.

IIS 7

Launch the Internet Information Services (IIS) Manager and install the SmartCardAuthenticationModule in the Modules Feature under IIS section like so:
1. Open IIS Manager and expand the web site / application to enable the module:
IIS7Manager

2. Choose the Modules Feature under the IIS section, then click Add Managed Module… in the Right hand Actions pane.

ModuleFeatures

3. The Add Managed Module dialog box will pop up. In the Name box, type in a name like SmartCardAuthenticationModule. The drop-down should contain an entry for the assembly SmartCardAuthentication.SmartCardAuthenticationModule that was detected from adding a reference to the project. When the reference is added, the SmartCardAuthentication.dll should have been put in the /bin folder of the application.

AddManagedModule

4. Click OK.
The steps above add the following XML to the web.config:
<configuration>
  ...
  <system.webServer>
    <modules>
      <add name="SmartCardAuthentication"
           type="SmartCardAuthentication.SmartCardAuthenticationModule"
           preCondition="" />
    </modules>
  </system.webServer>
  ...
</configuration>
NOTE: Only set preCondition=”managedHandler” if you want the Smart Card Authentication Module to protect ASP.NET pages. If you want the module to protect your images and other documents on the web server, make sure to leave preCondition empty.

IIS 5.1 / 6.0

For older versions of IIS, simply add the following XML to the web.config file in the root of the web application:

<?xml version="1.0"?>
<configuration>
  <system.web>
  ...
    <httpModules>
      <add name="SmartCardAuthentication"
           type="SmartCardAuthentication.SmartCardAuthenticationModule,
                                               SmartCardAuthentication"/>
    </httpModules>
  ...
  </system.web>
...
</configuration>

Customization

There are several events provided in the SmartCardAuthenticationModule that will override the Smart Card Authentication Module’s default behavior. These events can be overridden in the Global.asax.
The events are:
  • * Authenticate
  • * MembershipUserCreating
  • * MembershipUserCreated
  • * MembershipValidating
  • * FailedMembershipAuthentication
For the events to be properly wired up, they must be prefixed with the Module Name followed by an underscore. For example, to subscribe to the Authenticate event, the method name would be SmartCardAuthentication_Authenticate.

SmartCardAuthentication_Authenticate Event

The Authenticate event is used to override the default authentication behavior of the Smart Card Authentication Module.
By default, the Smart Card Authentication Module will authenticate all Smart Cards / Client Certificates allowed in by IIS unless Membership is used. The Module also will not assign any roles, unless using the ASP.NET RoleProvider. Using information from the X.509 certificate, authenticated users and retrieve their corresponding roles.
When implementing this event:

  • * For Authentication to be successful, attach a IPrincipal containing the SmartCardIdentity to the AuthenticationEventArgs.User property. Make sure to set the IsAuthenticated property to true.
  • * To signal the Smart Card Authentication Module that Authentication has failed, either set the AuthenticationEventArgs.User property to null, or attach the IPrincipal to the AuthenticationEventArgs.User property and set IsAuthenticated to false.
protected void SmartCardAuthentication_Authenticate(object sender,
                SmartCardAuthentication.AuthenticationEventArgs e)

{  // NOTE: e.Identity has the Smart Card Identity extracted by
  // the SmartCardAuthenticationModule  if (e.Identity != null)
  {
    // Write code to take SmartCard information from e.Identity
    // and:
    // 1. Authenticate: Check the user against the user data store
    // and check if they should be authenticated
    // e.g. From Active Directory, LDAP, Custom DB, etc.

    // FOR DEMO PURPOSES THIS HARD CODES IIdentity.IsAuthenticated
    // TO TRUE. ANYONE WITH A SMART CARD OR CLIENT CERTIFICATE
    // VALIDATED BY THE OS AND IIS WILL BE LOGGED IN.
    // It may be acceptable to hard code in certain situations
    // where Certificate Trust Lists (CTL) are configured
    // and properly restrictive in IIS
    e.Identity.IsAuthenticated = true;

    if (e.Identity.IsAuthenticated)
    {
      // 2. Authorize: Retrieve roles
      // e.g. From Active Directory, LDAP, Custom DB, etc.
      // string roles[]=
      //   DBAccess.GetRolesByPublicKeyhash(e.Identity.PublicKeyHash);
      // string roles[]=
      //       LDAPAccess.GetRolesByUPN(e.Identity.UserPrincipalName);
      // For Demo purposes, this uses HARD CODED ROLES
      string[] roles = new string[] { "Accounting", "Administrator" };

       // 3. Create a new Principal object using
       // retrieved roles and the Smart Card Identity (e.Identity)

       // This example will use GenericPrincipal, which works well
       // when you don't need a specific principal
       GenericPrincipal genericPrincipal =
                       new GenericPrincipal(e.Identity, roles);

       // 4. Attach the IPrincipal to the e.Context.User OR the
       // e.User to signal he SmartCardHttpModule that
       // authentication has been handled
       e.User = genericPrincipal;
    }
    else
    {
      // NOT AUTHENTICATED, make sure user is null to signal
      // the SmartCardHttpModule that authentication has been
      // handled
      e.User = null;
    }
  }
}

SmartCardAuthentication_MembershipUserCreating Event

The MembershipUserCreating event is used to override the Smart Card Authentication Module’s default behavior of Membership Account creation.
This event allows control over how membership accounts are created, including information that might also need added to the user’s profile, and whether the Membership accounts are enabled or disabled when created. When using something other than the e.Identity.Name and e.Identity.PublicKeyHash as the Membership username and password, ALSO implement the SmartCardAuthentication_MembershipValidating event as well to make sure Membership.ValidateUser() is called with the correct username and password.
When implementing this event:
  • * When creating the Membership User account, take care when setting the isApproved argument in the Membership.CreateUser() method. Only set it to true if the Identity is from a valid user of the system. Remember, anyone could present a false Client Certificate and attempt to spoof a Smart Card.
  • * Likewise, exercise caution when setting the MembershipEventArgs.Identity.IsAuthenticated property to true for the same reasons outlined above.
  • * After creating the Membership User account, assign the newly created MembershipUser to the MembershipEventArgs.MembershipUser property.
protected void SmartCardAuthentication_MembershipUserCreating(
                   object sender,
                   SmartCardAuthentication.MembershipEventArgs e)
{  // Account doesn't exist, add it  MembershipCreateStatus status;   // In this example, auto-enable the user (NOT USUALLY A GOOD IDEA unless
  // there is data available in back end systems that can validate the
  // Smart Card information as authentic). This is done by setting the
  // 6th argument of Membership.CreateUser() to true.  MembershipUser user = Membership.CreateUser(
                               e.Identity.Name,
                               e.Identity.PublicKeyHash,
                               e.Identity.EmailAddress,
                               null, null, true, null, out status);  if (status == MembershipCreateStatus.Success)
  {
    // Success, attach the newly created user to the Membership
    // eventArgs.
    e.MembershipUser = user;

    // Make sure to also set IsAuthenticated to true to signal the
    // Smart Card Module that this user is authenticated.
    e.Identity.IsAuthenticated = true;
  }  else  {
    // There was an error creating the account so throw the error.
    throw new MembershipCreateUserException(status);
  }
}

SmartCardAuthentication_MembershipUserCreated Event

The MembershipUserCreated event allows implementers the ability to do further Membership account or Profile configuration setup after the Smart Card Authentication Module creates the MembershipUser account using the default Membership Provider.
This event is raised right after the Smart Card Module automatically creates the membership account. This allows access to the automatically created Membership User account and also provides a place to automatically enable the Membership account or to notify the administrator that a new Membership account has been created and that action is required to validate and enable it.

SmartCardAuthentication_MembershipValidating Event

The MembershipValidating event is used to override the Smart Card Authentication Module’s default Membership validation behavior.
The MembershipValidating event provides a place to override the Smart Card Authentication Module’s default Membertship validation routine. This is useful if the Membership Username and Password need to be different than the default implementation.
When implementing this event:

  • * For a successful Membership Authentication, assign the MembershipUser to the MembershipEventArgs.MembershipUser property and set the MembershipEventArgs.Identity.IsAuthenticated to true.
  • * For an unsuccessful Membership Authentication, set the MembershipEventArgs.MembershipUser to null and the MembershipEventArgs.Identity.IsAuthenticated to false.
protected void SmartCardAuthentication_MembershipValidating(object sender,
                     SmartCardAuthentication.MembershipEventArgs e)
{  // Provide new Membership Validation  bool isUserValidated = false;  // Does Smart Card User have a Membership account?  if(Membership.FindUsersByName(e.Identity.Name).Count == 1)
  {
    // Yes, validate them
    isUserValidated = Membership.ValidateUser(e.Identity.Name,
    e.Identity.PublicKeyHash);
    // account might be marked Inactive, so set the IIdentity to
    // match Membership
    e.Identity.IsAuthenticated = isUserValidated;
    e.MembershipUser = Membership.GetUser(e.Identity.Name);

    if (!isUserValidated)
    {
      // Authentication with Membership provider failed.
      e.MembershipUser = null;
    }
  }  else  {
    throw new ApplicationException("Membership user not found.");
  }
}

SmartCardAuthentication_FailedMembershipAuthentication Event

The FailedMembershipAuthentication event is fired when an authentication failure occurs for any reason in the Smart Card Authentication Module, whether it’s Membership Auth failure or Smart Card Identity authentication failure.
This can mean one of several things:
  • * Someone provided a Client Certificate or SmartCard that is not authorized to use the site.
  • * Someone’s Smart Card / Client Certificate expired and needs renewed.
  • * The information registered in the Membership database doesn’t match the Smart Card / Client Certificate supplied data.
Since the X.509 certificates expire on Smart Cards and Client Certificates there may be a way to detect and automatically re-enroll User’s new X.509 certificate.
Additionally it may be used to do custom account lockout after several failed logins. If using a Membership Provider, use it’s account lockout mechanisms instead.
protected void SmartCardAuthentication_FailedMembershipAuthentication(
               object sender,
               SmartCardAuthentication.AuthenticationEventArgs e)
{  // Use e.Identity data retrieved from the X.509 certificate and
  // auto-enroll if able to verify the new X.509 certificate data.
  // DO NOT JUST TRUST THE NEW PUBLIC KEY HASH from e.Identity - IT COULD
  // ALLOW IN AN UNAUTHORIZED USER.

  // At Minimum, log failed log-ins and perhaps incorporate it into
  // workflow for re-enrolling users with expired certificates.}

Custom Unauthorized Page

To display a custom 401 Unauthorized page, include it in the customErrors node of the web.config like so and unauthorized users will be re-directed to it.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  ...
  <system.web>
    <customErrors mode="On" >
      <error statusCode="401" redirect="Unauthorized.aspx"/>
    </customErrors>
    ...
  </system.web>
  ...
</configuration>
Make sure to grant access to anonymous users to the Unauthorized page in the web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  ...
  <location path="Unauthorized.aspx">
    <system.web>
      <authorization>
        <allow users="?" />
        <deny users="*" />
      </authorization>
    </system.web>
  </location>
  ...
</configuration>

Download Source

You can download the source here.

Comments, questions, feedback, and feature requests welcome. Also if you run across any bugs let me know.

42 comments: