# # Authen::Challenge::Basic: Provides a trivial challenge/response protocol # to assist in authentication tasks. It provides for time-window # challenge/response sessions. # # Using this module, it's possible to autenticate both endpoints of # a transaction provided that a shared-secret was exchanged prior to # the session among the endpoints. As timestamps are part of the # protocol, some restrictions can be applied to the timing, to help # prevent hijacked connections. # # This is free software. You can use at will provided that proper # credit is given to the author(s). This module requires MD5. # # lem@cantv.net, 19980713 - Initial release # ############# package Authen::Challenge::Basic; $VERSION='0.1'; require 5.000; =head1 NAME Authen::Challenge::Basic - A Basic challenge/response authentication scheme. =head1 SYNOPSIS use Authen::Challenge::Basic; $server = Authen::Challenge->new ('Secret' => 'known2us', 'Timeout' => 30, 'Sync' => 10); $client = Authen::Challenge->new ('Secret' => 'known2us', 'Timeout' => 30, 'Sync' => 10); $challenge = $server->Challenge; $response = $client->Response($challenge); if ($server->Validate($challenge, $response)) { print "Hi master\n"; } else { print "Impostor!\n"; } =head1 DESCRIPTION Authen::Challenge::Basic provides a simple MD5-based challenge/response protocol allowing for mutual peer authentication in a session. The protocol includes timing information, so it is possible to introduce time constraints in the session to help prevent attacks that rely on adjusting the clock in one of the peers. This protocol requires a shared secret to be known only to the peers. The compromise of this secret also compromises this protocol, so it should be treated as a trusted password for that matter. Challenge/response sessions are not 'replayable' provided that the attacker ignores the shared secret. The sessions are also associated to the instance that produced the challenge. This means that it can only be Validate()'d by the instance that produced the challenge in the first place. The built-in random number generator from perl is used in this module. Hooks for better random number generators are planned soon to increase the relative strength of this protocol. In any case, the main security dependencies for this module are MD5 itself and the secrecy of the shared secret. The following functions are provided by this class. new() Creates a new instance of a challenge/response endpoint. It has three parameters that influence its behavior. Those can be seen next $server = Authen::Challenge::Basic->new ('Secret' => 'known2us', 'Timeout' => 30, 'Sync' => 10); 'Secret' is used to indicate the shared secret to use in this session. 'Timeout' specifies the lifespan, in seconds, for this transaction. This means that a succesful Validate() must occur within this many seconds to have a chance to be acknowledged. Reducing this value too much can cause problems as a slight ammount of load can make the session fail. 30 seconds is a reasonable default for many uses. If left unspecified, no timeout is enforced. 'Sync' indicates how strict are we with regard to the clock in our peer. The parameter contains the maximum offset in seconds between the clocks of the peers. The more strict we are (ie, the smaller the number), the less tolerant we are. If left unspecified, we'll allow any ammount of drift. It's specified in seconds. Challenge() Issues a new challenge. The object creating this challenge stores information about it that allows for later recognition and validation. The Challenge() is a typical function of the server, though a double Challenge/Response scenario allows mutual authentication such as in the following scheme: Client Server (1) C1 ----------------------> (2) <---------------------- R(C1), C2 (3) R(C2) ----------------------> On stage (2), the Client is authenticated to the server. On stage (3), both are mutually authenticated. This method is usually invoked like $challenge = $server->Challenge; $challenge will contain a printable string that must be passed to the peer in order for a response to be received. Response() This method takes a challenge and generates the required response. This is usually invoked as $response = $client->Response($challenge); Where $challenge contains a Challenge generated by a call to Challenge(). $response will contain a printable string that must be returned to the peer in order for it to be validated. Validate() This method verifies the correctness of the Challenge/Response session by insuring that: (a) The challenge was indeed generated from this instance and is pending validation (b) The time interval for the validation is acceptable (c) The shared secret was used by the peer to create the response Usually this method is invoked like this $r = $server->Validate($challenge, $response); It returns a true value to indicate a correct session where (a), (b) and (c) hold true or false otherwise. =head1 CAVEATS Note that this module helps insure that the peers were who they said they where provided that the shared secret is not known to any third parties. If this is not true, then anything could happen. Also, after the initial authentication, a network connection can be stolen or hijacked, rendering all of the tests useless. =head1 AUTHOR Luis E. Munoz =cut use MD5; sub new { my ($class, @opt) = @_; my ($r_param) = canon_params(@opt); $self = {'p' => $r_param, 'Error' => undef, 'Stamp' => '', 'Count' => 0, 'Alive' => {} }; bless $self, $class; return $self; } sub Error { my ($self) = @_; $self->{'Error'}; } sub Challenge { my ($self) = @_; my ($ctx) = new MD5; my ($Count) = sprintf("%05d", $self->{'Count'}++); my ($Stamp) = time; # Add the shared secret... $ctx->add($self->{'p'}->{'secret'}); # ... and a transaction counter... $ctx->add($Count); # ... and a perturbed timestamp, keeping it safe. $ctx->add($self->{'Stamp'} = $Stamp + rand(16384)); my ($result) = $ctx->hexdigest; $self->{'Alive'}->{$result} = $Stamp; $self->{'Error'} = undef; $result; } sub Response { my ($self, $challenge) = @_; my ($ctx) = new MD5; my ($Stamp) = time; $self->{'Error'} = undef; $ctx->add($challenge); $ctx->add($self->{'p'}->{'secret'}); $ctx->add($Stamp); $ctx->hexdigest . "/" . sprintf("%012d", $Stamp); } sub Validate { my ($self, $c, $r) = @_; my ($Stamp) = time; my ($mysignature, $this); # Insure the response is in a valid format... my ($signature, $tstamp) = split(/\//, $r, 2); if (!$tstamp or !$signature) { $self->{'Error'} = "Wrong response format"; return undef; } # Insure this challenge was issued by this instance... if (!($this = $self->{'Alive'}->{$c})) { $self->{'Error'} = "This challenge doesn't belong to this instance"; return undef; } undef($self->{'Alive'}->{$c}); # Insure that this response has come on time... if ($self->{'p'}->{'timeout'} && $self->{'p'}->{'timeout'} + $this < $Stamp) { $self->{'Error'} = "Response came too late"; return undef; } # Insure time sync between client and server... if ($self->{'p'}->{'sync'} && $self->{'p'}->{'sync'} + $this < $tstamp) { $self->{'Error'} = "Endpoints out of time-sync"; return undef; } # Insure a correct signature... $mysignature = new MD5; $mysignature->add($c); $mysignature->add($self->{'p'}->{'secret'}); $mysignature->add(int($tstamp)); if ($mysignature->hexdigest ne $signature) { $self->{'Error'} = "Wrong signature for this response"; return undef; } $c; } sub canon_params { my(@param) = @_; my(%param) = @param; my ($k, $n); foreach $k (keys %param) { $n = $k; $n =~ tr/A-Z/a-z/; $param{$n} = $param{$k}; undef($param{$k}) unless $n eq $k; } \%param; } 1;