November 10, 2010

Two Factor Authentication with Perforce


Two factor authentication has become a common paradigm in the modern enterprise. Two factor authentication requires both a regular password and a second identifier, commonly a random number generated by a security token. By requiring both of these items, we ensure that neither a lost token or a compromised password alone can give an intruder access to a sensitive system.

Two factor authentication is frequently used in VPN systems. Once a user is inside the VPN firewall, she has access to the internal systems necessary to do her work. These internal systems typically have their own, separate authentication system. Systems such as Outlook would typically require a corporate (LDAP or Active Directory) password, while systems such as Perforce maintain their own user database and use their own passwords, or integrate with the corporate directory server for password checking.

This approach, where security from intruders is the responsibility of the firewall or VPN system, and Perforce only handles internal security, is still the norm in most environments. In other words, the firewall should keep us safe from intruders, and Perforce will only make sure that authorized users do not see parts of the depot that we choose to restrict.

In some cases, though, we can't rely on the VPN or firewall, because we need to allow external access to Perforce. Or, perhaps we want to use a stronger defense-in-depth strategy. Since many Perforce sites keep confidential information in Perforce, we might not want to allow an intruder easy access to the depots if they manage to break into a workstation on the corporate network.

By using security level 3, we at least insist that a user log in with a password periodically before they can access Perforce. An intruder would not be able to access Perforce by using a password stored in the environment, registry, or configuration file.

If we have a two factor authentication system available, then we may be able to have Perforce authenticate against it using an authentication trigger. That would offer additional protection if a user's password is compromised.

Although Perforce doesn't offer any turn-key support for two factor authentication, it is fairly easy to write an authentication trigger that uses it. An authentication trigger is given the user name and password when someone tries to log in to Perforce, and is responsible for validating the name and password. If we know that our second authentication factor is always a six digit number, then we can ask our users to enter this number in combination with their password. Our trigger can then assume that the first six characters of the password are actually the number from the authentication token, and the rest is their personal password. The trigger can then ask the two factor authentication system to validate the six digit number and password.

Below is a snippet from a perl script that demonstrates this technique. (We do not actually authenticate the password in this example.)

## Read user name from command line arguments
my $UserName = shift @ARGV;

## Get password from the user on the command line.
my $UserPassword = <STDIN>;
chomp $UserPassword;

## Split out six-digit token.
my $Token = substr $UserPassword, 0, 6;
my $Password = substr $UserPassword, 6;

## Verify token and password
my $Valid = 1;
unless($Valid) {
    die "\nInvalid authentication attempt";

Of course, there are some assumptions and limitations in this example:

  • The two factor authentication system has an API that allows us to use it for authentication calls from a trigger.
  • Our users must enter the six digit number followed by their password.
  • We need to make some allowances for authenticating non-human accounts, such as accounts used for continuous build systems.
  • We don't do any error checking to make sure that the password is long enough to contain the six digit number.

This solution isn't perfect -- ideally, we'd want to prompt the user separately for their password and second authentication factor -- but its simple and easy to implement.